Tom
Tom

Reputation: 287

How to stop child components resetting on parent setState

I have been searching for a while but can't seem to find a concrete answer to this problem. I have encountered this many times and overcame them by using "hacky" solutions. What is the recommended/standard way to get around child components re-rendering when setting parent state.

I have made a very simple example to show this problem where we have a left side child component with a count state and a right side child component which displays text when a button on the left side is clicked.

Example.js

import React, { useState } from "react";

import "../css/pages/Example.css";

function Example() {
  const [rightText, setRightText] = useState();

  const LeftSide = () => {
    const [count, setCount] = useState(0);

    return (
      <div className="egleft">
        <p>{count}</p>
        <button
          onClick={() => {
            setCount(count + 1);
           }}
        >
          Add
        </button>
        <button
          onClick={() => {
            setRightText("hello world");
          }}
        >
          Update right
        </button>
      </div>
    );
  };

  const RightSide = () => {
    return <div className="egright">{rightText && <p>{rightText}</p>}</div>;
  };

  return (
    <div className="wrapper">
      <LeftSide />
      <RightSide />
    </div>
  );
}

export default Example;

Example.css

.wrapper {
  width: 300px;
  height: 300px;

  border: 1px solid red;

  display: flex;
}

.egleft {
  border: 1px solid green;
  width: 50%;
}
.egleft p,
.egright p {
  color: black;
}

.egright {
  width: 50%;
  border: 1px solid blue;
}

To replicate: click add button to increment value on left side.. then click update right. You will see due to the parent's setState, left side is re-rendered, causing its own count state to be reset to the initial value.

Here is a gif showing the problem

Thanks

Upvotes: 1

Views: 2288

Answers (2)

Alykam Burdzaki
Alykam Burdzaki

Reputation: 694

The issue is you are creating the LeftSide (and it's state) and RightSide components on the fly each time the Example component re-renders.

It's not common practice to create child components inside the render function of the parent as it creates an unecessary overhead (creating a new function every render instead of using the same existing function). You can split up the components into multiple functions in the root of the file, or multiple files, and compose them like @dejan-sandic answer.

But if creating the child component inside the render function is a must. You can use the useMemo hook to stop react from recreating your child component:

import React, { useState, useMemo } from "react";

import "../css/pages/Example.css";

function Example() {
  const [rightText, setRightText] = useState();

  const LeftSide = useMemo(() => () => {
    const [count, setCount] = useState(0);

    return (
      <div className="egleft">
        <p>{count}</p>
        <button
          onClick={() => {
            setCount(count + 1);
           }}
        >
          Add
        </button>
        <button
          onClick={() => {
            setRightText("hello world");
          }}
        >
          Update right
        </button>
      </div>
    );
  }, []);

  const RightSide = () => {
    return <div className="egright">{rightText && <p>{rightText}</p>}</div>;
  };

  return (
    <div className="wrapper">
      <LeftSide />
      <RightSide />
    </div>
  );
}

export default Example;

But I would advise against it, as it can become unnecessarily complex, unless for something extremely dynamic.

Upvotes: 3

Dejan Sandic
Dejan Sandic

Reputation: 451

You are defining both RightSide and the LeftSide components inside of Example. You can't do that because those two components will be created every time` Example component renders, which is happening after every statechange.

const RightSide = () => {
    return <div className="egright">{rightText && <p>{rightText}</p>}</div>;
};

Do this in stead:

const LeftSide = ({ setRightText }) => {
   const [count, setCount] = useState(0);

   return (
      <div className="egleft">
         <p>{count}</p>
         <button
         onClick={() => {
            setCount(count + 1);
            }}
         >
         Add
         </button>
         <button
         onClick={() => {
            setRightText("hello world");
         }}
         >
         Update right
         </button>
      </div>
   );
};

const RightSide = ({ rightText }) => {
   return <div className="egright">{rightText && <p>{rightText}</p>}</div>;
};

function Example() {
  const [rightText, setRightText] = useState();

  return (
    <div className="wrapper">
      <LeftSide  rightText={rightText} />
      <RightSide setRightText={setRightText} />
    </div>
  );
}

Upvotes: 1

Related Questions