How to Design useState and useEffect Hooks: A Beginner’s Guide

Biswas Prasana Swain - Nov 6 - - Dev Community

When developing modern applications, especially web apps, you'll often need to manage data that changes over time. For example, if a user clicks a button, we may want to update the display or fetch new data from a server. Hooks like useState and useEffect help us handle this smoothly. Let's break down how these concepts work and explore how to design them step-by-step.


What Are useState and useEffect?

  • useState: This hook allows you to add state to a component. Think of "state" as any data that a component needs to remember between renders, like a counter or a list of items.
  • useEffect: This hook lets you perform actions after the component renders, such as fetching data, setting up timers, or changing the page title.

To make this guide easy to understand, we’ll break each hook down to its essential logic and build from there.


Designing the useState Hook

1. Understanding State Management Basics

Imagine you have a simple counter app. Each time you press a button, the number goes up by 1. To make this work, you need to store the current count somewhere and update it every time the button is clicked.

2. Goal of useState

useState should:

  • Store a value.
  • Provide a way to update that value.
  • Trigger a re-render (or re-draw) of the component when the value changes.

3. Basic Concept of useState

Here’s a basic breakdown of how useState might work under the hood:

  • We need a variable to hold the value of our state (e.g., a counter).
  • We need a function to update this value.
  • When the value updates, we need to re-render the component to reflect the new value.

4. Designing useState from Scratch

Let's define a simple structure for useState:

  1. Initial Setup: Create a function called useState that takes an initial value as input.
  2. Return Current Value and Update Function: The function should return two things:
    • The current value.
    • A function that can update this value.
  3. Trigger Re-render: Ensure that any update to the state causes the component to re-render (we’ll simplify this part in our example).

Example Code

Here's how a simple version of useState might look:

function useState(initialValue) {
    // Step 1: Create a variable to hold the current state value
    let currentState = initialValue;

    // Step 2: Define a function to update this value
    function setState(newValue) {
        // Update the state
        currentState = newValue;

        // Simulate a re-render (you’d do this differently in a real application)
        render();
    }

    // Step 3: Return the state and the function to update it
    return [currentState, setState];
}

// Usage example:
const [count, setCount] = useState(0);
console.log(count); // Outputs: 0
setCount(1);         // Updates state to 1
console.log(count);  // Outputs: 1 (in real use, this would trigger re-rendering)
Enter fullscreen mode Exit fullscreen mode

Designing the useEffect Hook

While useState handles local data, useEffect allows us to perform "side effects," like fetching data or updating the document title. A side effect is any interaction with the outside world.

1. Goal of useEffect

useEffect should:

  • Run a function after the component renders.
  • Optionally clean up any effects when the component is removed.
  • Optionally run again if specified data changes.

2. Basic Concept of useEffect

The main parts of useEffect are:

  1. Effect Function: This is the action you want to perform after rendering, such as logging a message, fetching data, or starting a timer.
  2. Dependency Array: This optional list tells useEffect when to re-run. If any value in this list changes, the effect will run again.

3. Designing useEffect from Scratch

Let’s set up a simple structure for useEffect:

  1. Function Execution: Create a function called useEffect that takes two parameters:
    • An effect function to run.
    • An optional dependency array.
  2. Run Effect After Render: Ensure that the effect function runs after the component renders.
  3. Run Effect on Dependency Change: If a dependency array is provided, only re-run the effect when one of the dependencies changes.

Example Code

Here's a basic version of useEffect:

let previousDeps;  // To store previous dependencies

function useEffect(effectFunction, dependencies) {
    // Step 1: Check if dependencies have changed
    const hasChanged = dependencies
        ? !previousDeps || dependencies.some((dep, i) => dep !== previousDeps[i])
        : true;

    // Step 2: Run the effect function if dependencies changed
    if (hasChanged) {
        effectFunction();
        previousDeps = dependencies;  // Update the previous dependencies
    }
}

// Usage example:
useEffect(() => {
    console.log("Effect has run!");

    // Simulate cleanup if needed
    return () => console.log("Cleanup effect!");
}, [/* dependencies */]);
Enter fullscreen mode Exit fullscreen mode

Putting It All Together: Example Usage

Let’s simulate a component using both useState and useEffect.

function Component() {
    // Initialize state with useState
    const [count, setCount] = useState(0);

    // Log a message each time count changes with useEffect
    useEffect(() => {
        console.log(`Count has changed to: ${count}`);
    }, [count]);  // Re-run effect if count changes

    // Simulate user interaction
    setCount(count + 1);
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • We create a count state with useState.
  • We use useEffect to log a message whenever count changes.
  • Each time setCount updates the count, it triggers a re-render, causing useEffect to run again if count has changed.

Summary

Designing useState and useEffect involves:

  1. Storing values (useState) and providing a way to update and re-render them.
  2. Running functions after rendering (useEffect), with options for cleanup and dependency tracking.

These hooks help you build dynamic and interactive applications, whether it’s for simple counters, fetching data, or more complex state management. With a foundation in these hooks, you're well-equipped to create apps that respond to user actions and real-time data changes!

. . . . . . .