Instagram
youtube
Facebook
Twitter

useState and useEffect

Introduction

In the previous lesson, we learned about the concept of Hooks in React. Now, let’s dive deeper into two foundational hooks: useState and useEffect. These hooks are commonly used for managing component state and handling side effects, making them essential for building dynamic and interactive React applications.

In this lesson, you’ll learn:

  • How to manage state in functional components with useState
  • How to handle side effects using useEffect
  • The syntax and typical use cases for each hook

What is useState?

The useState hook allows you to add and manage state within a functional component. It provides a way to store data that can change over time, such as form inputs, counters, or toggles.

Syntax of useState

const [state, setState] = useState(initialValue);
  • state is the current state value.
  • setState is a function that allows you to update the state.
  • initialValue is the starting value for the state, which can be any data type (number, string, array, object, etc.).

Each time the setState function is called, React re-renders the component with the updated state value.

Example: Using useState for a Counter

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

In this example:

  • count is the state variable initialized to 0.
  • setCount is the function that updates count each time the button is clicked.

Each time setCount is called, React updates the component with the new count value, and the component re-renders to reflect this change.


What is useEffect?

The useEffect hook allows you to perform side effects in functional components. Side effects include actions like fetching data from an API, setting up subscriptions, or updating the DOM.

Syntax of useEffect

useEffect(() => {
  // Effect logic goes here

  return () => {
    // Optional cleanup logic
  };
}, [dependencies]);
  • The first argument is a callback function containing the side effect.
  • The second argument is an optional dependency array. This array determines when the effect should re-run. If it’s empty ([]), the effect only runs once, like componentDidMount in a class component.

Example: Using useEffect to Update the Document Title

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

function DocumentTitleUpdater() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

In this example:

  • The useEffect hook updates the document title every time count changes.
  • The dependency array [count] ensures that the effect only runs when count changes, preventing unnecessary re-renders.

Using useEffect for Data Fetching

One of the most common use cases for useEffect is to fetch data from an API when a component mounts.

Example: Fetching Data with useEffect

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

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, []); // Empty dependency array means this runs only once on mount

  if (loading) return <p>Loading...</p>;
  return <div>{JSON.stringify(data)}</div>;
}

In this example:

  • The fetch call fetches data from an API only once when the component mounts, as indicated by the empty dependency array [].
  • Once the data is fetched, the state is updated, triggering a re-render to display the data.

Dependency Array in useEffect

The dependency array is crucial for controlling when useEffect runs. It determines when the effect is re-applied by React.

  1. No Dependency Array: The effect runs after every render.

    useEffect(() => {
      // Effect logic here
    });
    
  2. Empty Dependency Array ([]): The effect runs only once when the component mounts, similar to componentDidMount.

    useEffect(() => {
      // Effect logic here
    }, []);
    
  3. Array with Specific Dependencies: The effect runs whenever any dependency in the array changes.

    useEffect(() => {
      // Effect logic here
    }, [dependency1, dependency2]);
    

Example with Dependency Array

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

function EffectWithDependency() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`Count has changed to ${count}`);
  }, [count]); // Runs only when `count` changes

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

In this example:

  • The useEffect hook only logs to the console when count changes, thanks to the dependency array [count].

Effect Cleanup with useEffect

Sometimes, side effects require cleanup to prevent memory leaks, such as removing event listeners or canceling network requests. You can handle this with the cleanup function inside useEffect, which runs when the component unmounts or before the next effect.

Example: Cleaning Up an Interval

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    return () => clearInterval(interval); // Cleanup to clear interval on unmount
  }, []);

  return <p>Seconds: {seconds}</p>;
}

In this example:

  • The setInterval function increments the seconds state every second.
  • The cleanup function clearInterval ensures that the interval is cleared when the component unmounts, preventing memory leaks.

Common Pitfalls with useState and useEffect

Here are some common issues to watch out for when working with these hooks:

  1. Missing Dependencies: Always include all variables that change within the effect in the dependency array to prevent unexpected behavior.
  2. Infinite Loops: Avoid setting state directly within useEffect without a conditional, as this can cause infinite loops.
  3. Multiple Effects: Split complex logic across multiple useEffect calls if necessary, as each hook can handle separate concerns.

Conclusion

In this lesson, we covered:

  • How to use the useState hook to add and update state in functional components
  • How to use the useEffect hook to handle side effects in functional components
  • The role of the dependency array and how to manage cleanup with useEffect

The useState and useEffect hooks are fundamental to functional components, enabling you to manage state and side effects effectively. In the next lesson, we’ll explore Custom Hooks, which allow you to encapsulate and reuse component logic across your React applications.