ReactHustle

How to Update State when Props Change in React

Jasser Mark Arioste

Jasser Mark Arioste

How to Update State when Props Change in React

Hello, hustlers! In this tutorial, you'll learn how to update the state when props change. 

The Problem #

Sometimes, we have a local component state that depends on the props however the local state is not updated when the props change.

Consider the following code:

import classNames from "classnames";
import { useEffect, useState } from "react";

export default function ParentComponent() {
  const [scrollYPos, setScrollYPos] = useState(0);
  useEffect(() => {
    window.addEventListener("scroll", () => {
      setScrollYPos(window.scrollY);
    });
  }, []);
  return (
    <div className="h-[2000px] container mx-auto">
      <Navbar scrollYPos={scrollYPos} />
    </div>
  );
}

type NavbarProps = {
  scrollYPos: number;
};

export function Navbar({ scrollYPos }: NavbarProps) {
  const [scrolled] = useState(scrollYPos >= 100);

  return (
    <div
      className={classNames({
        "fixed top-0 w-screen bg-indigo-500 text-white": scrolled,
        "w-full bg-transparent text-indigo-500": !scrolled,
        "border-b p-2": true,
      })}
    >
      Navbar
    </div>
  );
}
123456789101112131415161718192021222324252627282930313233343536

Explanation:

Here we have the Navbar component that depends on the scrollYPos props. It has a local component state scrolled that decides if the Navbar will be fixed to the top of the screen. 

In the ParentComponent, the scrollYPos state changes when the user scrolls. Now, you might think that the <Navbar/> component will re-initialize the useState hook on line 23 but this is not the case. The scroll position remains the same.

Below is the current output. Once the user scrolls, nothing happens to the navbar.

React Update State when Props Change - Output 1

Solution #

The solution is very simple. There are two choices you can make:

  1. Use the useEffect hook to listen if the scrollYPos prop changes.
  2. Remove the useState and rely on a derived state. This is usually the cleaner approach but if you can't use it, you can still use option 1. This is my preferred option
  3. Re-render the Navbar component using the key prop.

Approach 1- Using useEffect hook #

We can use the useEffect hook to listen to changes to props and update our state this way. For example:

// ... omitted for brevity
type NavbarProps = {
  scrollYPos: number;
};

export function Navbar({ scrollYPos }: NavbarProps) {
  const [scrolled, setScrolled] = useState(scrollYPos >= 1);

  useEffect(() => {
    setScrolled(scrollYPos >= 1);
  }, [scrollYPos]); // add scrollYPos as a dependency
  return (
    <div
      className={classNames({
        "fixed top-0 w-screen bg-indigo-500 text-white": scrolled,
        "w-full bg-transparent text-indigo-500": !scrolled,
        "border-b p-2": true,
      })}
    >
      Navbar
    </div>
  );
}
1234567891011121314151617181920212223

Now, we can see that the Navbar changes when the user scrolls down the page:

React Update State When Props Change - Example 2

Approach 2 - Computing the Derived State - A Cleaner approach #

A cleaner approach would be to get rid of the useState and useEffect and calculate the scrolled state immediately. This is useful if you don't need a local state in your component.

For example:

// omitted for brevity
type NavbarProps = {
  scrollYPos: number;
};

export function Navbar({ scrollYPos }: NavbarProps) {
  // const [, setScrolled] = useState(scrollYPos >= 1);

  // useEffect(() => {
  //   setScrolled(scrollYPos >= 1);
  // }, [scrollYPos]);

  const scrolled = scrollYPos >= 1;
  return (
    <div
      className={classNames({
        "fixed top-0 w-screen bg-indigo-500 text-white": scrolled,
        "w-full bg-transparent text-indigo-500": !scrolled,
        "border-b p-2": true,
      })}
    >
      Navbar
    </div>
  );
}
12345678910111213141516171819202122232425

Approach 3 - Using key prop in the ParentComponent #

The key prop can also be used by all components to trigger a complete re-initialization or reset of the component. This technique is useful if you want to reset components like forms. 

Here's an example of using they key prop:

import classNames from "classnames";
import { useEffect, useState } from "react";

export default function ParentComponent() {
  const [scrollYPos, setScrollYPos] = useState(0);
  useEffect(() => {
    window.addEventListener("scroll", () => {
      setScrollYPos(window.scrollY);
    });
  }, []);
  return (
    <div className="h-[2000px] container mx-auto">
      <Navbar scrollYPos={scrollYPos} key={scrollYPos} />
    </div>
  );
}

type NavbarProps = {
  scrollYPos: number;
};

export function Navbar({ scrollYPos }: NavbarProps) {
  const [scrolled] = useState(scrollYPos >= 1);

  return (
    <div
      className={classNames({
        "fixed top-0 w-screen bg-indigo-500 text-white": scrolled,
        "w-full bg-transparent text-indigo-500": !scrolled,
        "border-b p-2": true,
      })}
    >
      Navbar
    </div>
  );
}
123456789101112131415161718192021222324252627282930313233343536

Here, we make sure to pass a different key to trigger a reset of the Navbar component. On line 23, we still have a useState hook but we have to don't do anything.

Conclusion #

You learned three different ways to update the state when the props changes for React functional components. Make sure you use the second approach whenever possible, and make your best judgment on whether to use the first or third approach. 

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on GitHub.

Resources #

Credits: Image by Ingo Jakubke from Pixabay

Share this post!

Related Posts