Emile Frey
Emile Frey's Dev Blog

Emile Frey's Dev Blog

Pitfalls of nesting React Functional Components 😫

Pitfalls of nesting React Functional Components 😫

...and how to avoid them.😄

Emile Frey's photo
Emile Frey

Published on Aug 2, 2021

3 min read

One of the most common mistakes I see early React developers make is nesting functional components.

It's easy/tempting to do. You're working on a new functional component, you want to abstract an aspect of the component, and you create a new component inside of your current component. Or you need the parent's state inside the child and putting the "child" inside the parent component prevents you from having to drill props down. It works...sort of. But more times than none, nesting a component inside of another component results in unexpected behavior and unnecessary (and costly) renders.

Let's dive right in.

Problem:

Suppose you have a functional component with a nested functional component as follows:

import React, { useState } from "react";

const Demo = () => {
  const [parentState, setParentState] = useState(false);

  const NestedComponent = () => {
    const [innerState, setInnerState] = useState("");

    const handleOnChange = (e) => {
      setInnerState(e.target.value);
    };

    return (
      <div style={{ margin: "20px", fontWeight: 500 }}>
        <div>Parent State: {parentState ? "true" : "false"}</div>
        <div style={{ marginTop: "10px" }}>User Input:</div>
        <input value={innerState} onChange={handleOnChange} />
      </div>
    );
  };

  return (
    <div style={{ textAlign: "center", backgroundColor: "red", padding: "20px"}}>
      <h3>Nested Component Demo</h3>
      <button
        onClick={() => setParentState(!parentState)}
        style={{ margin: "20px" }}
      >
        Toggle Parent State
      </button>
      <NestedComponent />
    </div>
  );
};

Below is a demo, but before you play around, what do you think will happen if you enter text and then click the Toggle Parent State button? If you guessed that the user's computer will catch on fire, you guessed incorrectly. But you probably guessed that the user's input would disappear. In that case, you'd be correct!

I think it's important to understand why. When the user enter's input, the value in stored in innerState within the NestedComponent. The behavior of the component appears to be ok at first, but then the user clicks the button, which in turn updates the parent component's state, causing a re-render. At that moment, NestedComponent is recalculated and the innerState is set to the initial value in the useState (empty string).

Solution:

In order to prevent this undesired behavior, you simply need to move the NestedComponent outside of the parent component (can be in the same file or another file as an export) and pass the parent state to the child component:

import React, { useState } from "react";

const NonNestedComponent = (props: { parentState: boolean }) => {
  const [innerState, setInnerState] = useState("");

  const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInnerState(e.target.value);
  };

  return (
    <div style={{ margin: "20px", fontWeight: 500 }}>
      <div>Parent State: {props.parentState ? "true" : "false"}</div>
      <div style={{ marginTop: "10px" }}>User Input:</div>
      <input value={innerState} onChange={handleOnChange} />
    </div>
  );
};

const NestedComponentDemo = () => {
  const [parentState, setParentState] = useState(false);
  return (
    <div
      style={{ textAlign: "center", backgroundColor: "green", padding: "20px" }}
    >
      <h3>Nested Component Demo</h3>
      <button
        onClick={() => setParentState(!parentState)}
        style={{ margin: "20px" }}
      >
        Toggle Parent State: {parentState ? "true" : "false"}
      </button>
      <NonNestedComponent parentState={parentState} />
    </div>
  );
};

Conclusion:

Avoid nesting React components inside other components. There are so many things that can go wrong with this approach...and no obvious benefits!

 
Share this