≡ Menu

This tutorial demonstrates the ideal use case for the experimental useEffectEvent hook: creating stable, non-reactive callbacks that access fresh state or props without forcing your primary useEffect hook to re-run.

The Goal: Stable Auto-Saving

We want an editor component that automatically saves its content to an API every 5 seconds.

  • Requirement 1 (Stability): The 5-second timer/interval must be set up only once when the component mounts. It should not reset every time the user types.

  • Requirement 2 (Freshness): The save function must always read the latest text the user has typed (the latest content state).

Step-by-Step Implementation

Step 0: Setup and API Mock

We start with the basic component structure and mock the API function that handles the saving logic.

import { useEffect, useState, useEffectEvent } from 'react';

// Mock API function for demonstration
const api = { 
  save: (data) => console.log('Saving latest content:', data.substring(0, 20) + '...') 
};

function DocumentEditor() {
  const [content, setContent] = useState('');
  // ... (Steps 1 & 2 will go here)
  
  return (
    <div>
      <h3>Document Editor (Saves every 5s)</h3>
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        rows="10"
        cols="50"
        placeholder="Start typing..."
      />
    </div>
  );
}

Step 1: Define the Effect Event (The Non-Reactive Action)

The core saving logic is defined here. By wrapping it in useEffectEvent, we ensure that the function doSave always has a stable identity across re-renders, yet the code inside (which uses content) always sees the latest state.

// 1. Define the Effect Event (The Stable Function)
const doSave = useEffectEvent(() => {
  //  This function ALWAYS sees the LATEST 'content' state.
  if (content.trim() !== '') {
    api.save(content);
  }
});
  • Benefit: This function can be safely called from inside useEffect without being listed as a dependency.


Step 2: Define the Effect (The Reactive Setup)

Now we set up the interval using a standard useEffect. Since the function we call (doSave) is stable, the dependency array remains empty, fulfilling our stability requirement.

// 2. Define the Effect (The Setup Logic)
useEffect(() => {
  console.log('--- Setting up auto-save interval ---');
  
  // The interval calls the stable 'doSave' event
  const intervalId = setInterval(() => {
    doSave(); // Calls the stable event which reads the fresh 'content'
  }, 5000);

  // Cleanup: Clears the interval when the component unmounts or effect re-runs
  return () => {
    console.log('--- Clearing auto-save interval ---');
    clearInterval(intervalId);
  };
}, []); //  Dependency Array: Empty means it only runs ONCE on mount.
  • Result: The useEffect sets the timer once. Typing into the textarea updates content, but it does not trigger the useEffect to re-run and reset the timer.

Every 5 seconds, the stable doSave function runs and accesses the current, fresh content.

Summary of the Complete Component:

import { useEffect, useState, useEffectEvent } from 'react';

const api = { 
  save: (data) => console.log('Saving latest content:', data.substring(0, 20) + '...') 
};

function DocumentEditor() {
  const [content, setContent] = useState('');
  
  // 1. STABLE ACTION: This reads fresh state (content) but has a fixed identity.
  const doSave = useEffectEvent(() => {
    if (content.trim() !== '') {
      api.save(content);
    }
  });

  // 2. STABLE SETUP: This sets up the non-restarting timer.
  useEffect(() => {
    console.log('--- Setting up auto-save interval ---');
    const intervalId = setInterval(() => {
      doSave(); // Calls the stable event
    }, 5000);

    return () => {
      console.log('--- Clearing auto-save interval ---');
      clearInterval(intervalId);
    };
  }, []); // Only runs once.

  return (
    <div>
      <h3>Document Editor (Saves every 5s)</h3>
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        rows="10"
        cols="50"
        placeholder="Start typing..."
      />
    </div>
  );
}

Have any questions about the code above?

Leave them in the comments section below!

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }

JavaScript, like many programming languages, provides ways to store data in memory using variables.

However, the evolution of JavaScript has introduced different keywords for declaring variables, each with its own nuances and implications.

For newcomers and seasoned developers alike, understanding the distinctions between var, let, and const is crucial for writing clean, predictable, and maintainable code.

Let’s dive into the core differences:

1. var: The Old Guard (and Its Quirks)

var was the original way to declare variables in JavaScript. While still functional, it comes with a few characteristics that can lead to unexpected behavior if not understood.

Function Scope: Variables declared with var are function-scoped. This means they are accessible anywhere within the function they are declared in, regardless of block statements (like if statements or for loops).

function exampleVar() {
  if (true) {
    var x = 10;
  }
  console.log(x); // Output: 10 (accessible outside the if block)
}
exampleVar();

console.log(x); // Error: x is not defined (not accessible outside the function)

Hoisting: var declarations are “hoisted” to the top of their function scope. This means that you can use a var variable before it’s actually declared in the code, although its value will be undefined until the declaration is reached.

function hoistExample() {
  console.log(y); // Output: undefined
  var y = 20;
  console.log(y); // Output: 20
}
hoistExample();

Re-declaration and Re-assignment: var allows for both re-declaration and re-assignment within the same scope without error. This can sometimes lead to accidental overwriting of variables.

var name = "Alice";
var name = "Bob"; // No error, 'name' is re-declared
console.log(name); // Output: Bob

var age = 30;
age = 31; // Re-assigned
console.log(age); // Output: 31

Global Object Property: When declared outside of any function, var variables become properties of the global window object (in browsers).

var globalVar = "I'm global!";
console.log(window.globalVar); // Output: I'm global!

2. let: The Modern, Block-Scoped Variable

Introduced in ECMAScript 2015 (ES6), let addresses many of the shortcomings of var. It’s now the preferred way to declare variables when you expect their values to change.

Block Scope: The most significant difference is that let variables are block-scoped. A block is any code enclosed in curly braces {} (e.g., if statements, for loops, while loops, or simply a standalone block).

function exampleLet() {
  if (true) {
    let x = 10;
    console.log(x); // Output: 10
  }
  // console.log(x); // Error: x is not defined (not accessible outside the if block)
}
exampleLet();

No Re-declaration (within the same scope): You cannot re-declare a let variable within the same block scope. This helps prevent accidental overwriting.

let city = "New York";
// let city = "London"; // Error: Identifier 'city' has already been declared
city = "London"; // This is allowed (re-assignment)
console.log(city); // Output: London

Hoisting (but with a “Temporal Dead Zone”): While let declarations are technically hoisted, they are not initialized. Accessing a let variable before its declaration results in a ReferenceError. This period is known as the “Temporal Dead Zone” (TDZ).

function tdzExample() {
  // console.log(a); // Error: Cannot access 'a' before initialization
  let a = 1;
  console.log(a); // Output: 1
}
tdzExample();

Not a Global Object Property: Unlike var, let variables declared globally do not become properties of the window object.

3. const: For Unchangeable Values

Also introduced in ES6, const is used to declare constants – values that, once assigned, cannot be re-assigned.

Block Scope: Like let, const variables are block-scoped.

function exampleConst() {
  const PI = 3.14159;
  if (true) {
    const GREETING = "Hello";
    console.log(GREETING); // Output: Hello
  }
  // console.log(GREETING); // Error: GREETING is not defined
}
exampleConst();

No Re-assignment: This is the defining characteristic of const. Once a value is assigned, you cannot change it.

const BIRTH_YEAR = 1990;
// BIRTH_YEAR = 1991; // Error: Assignment to constant variable.

Initialization Required: You must assign a value to a const variable at the time of its declaration.

// const MY_CONSTANT; // Error: Missing initializer in const declaration
const MY_CONSTANT = "Some Value";

Caveat with Objects and Arrays: While you cannot re-assign a const variable that holds an object or an array, you can modify the properties of the object or the elements of the array. The const declaration only prevents re-assignment of the binding itself, not the internal contents of the bound value.

const person = {
  name: "John",
  age: 30
};
person.age = 31; // Allowed
console.log(person); // Output: { name: 'John', age: 31 }

// person = { name: "Jane" }; // Error: Assignment to constant variable.

const numbers = [1, 2, 3];
numbers.push(4); // Allowed
console.log(numbers); // Output: [1, 2, 3, 4]

// numbers = [5, 6]; // Error: Assignment to constant variable.

Quick Comparison Table

Feature var let const
Scope Function Scope Block Scope Block Scope
Hoisting Yes Yes (TDZ) Yes (TDZ)
Re-declaration Yes No No
Re-assignment Yes Yes No
Initialization Optional Optional Required
Global Object Property Not a property Not a property

When to Use Which?

  • const (Default Choice): If you can, always start with const. It enforces immutability for the variable’s binding, which helps prevent accidental re-assignments and makes your code more predictable. Only if you know the value will change, move to let.

  • let (When Value Changes): Use let when you declare a variable whose value is expected to change over time, such as a counter in a loop, a user’s input that might be updated, or a temporary variable that holds different values throughout a function’s execution.

  • var (Avoid, If Possible): In modern JavaScript development, it’s generally recommended to avoid var. Its function scoping and hoisting behavior can lead to subtle bugs and make code harder to reason about. Most linters will even warn you about its usage.

By adopting let and const, you embrace best practices that lead to more robust, readable, and less error-prone JavaScript code.

Happy coding!

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }