Search code examples
reactjsreact-hooksuse-effect

What is the issue when consuming an state in child component after useEffect?


I have the following situation that I can't explain.

What I'm trying to do, is to use an state variable from a parent component in a child component. When I load some new values into this state using a 'useEffect' call, only the Second component renders correctly. In this component I use 'props' variable to access the values.

The First component only renders what I initially set in the 'useState' call of the parent, ignoring the new state set in the useEffect call. In this component I set its state initially using the same props property.

Why is this occurring?

Here is the code and a snippet of the render.

App.js

import './App.css';
import First from './First';
import Second from './Second';
import { useEffect, useState } from 'react';

function App() {
  const [someVar, setSomeVar] = useState({dos:['a']});

  useEffect(()=>{
    console.log('useEffect')
    setSomeVar({dos:['a', 'b', 'c']})
  }, [])

  return (
    <div className="App">
      <header className="App-header">
        <First letters={someVar}></First>
        <hr/>
        <Second letters={someVar}></Second>
      </header>
    </div>
  );
}

export default App;

First.js

import { useState } from "react";

export default function First(props) {
  const [letters, setLetter] = useState(props.letters.dos);

  return (
    <div>
      {letters.map((letter) => {
        return <span key={letter}>{letter}</span>;
      })}
    </div>
  );
}

Second.js

export default function Second(props) {

  return (
    <div>
      {props.letters.dos.map((letter) => {
        return <span key={letter}>{letter}</span>;
      })}
    </div>
  );
}

Result

enter image description here


Solution

  • The value passed to useState is only used by React when a component is first mounted to determine the initial state value. On every future render, the state value used by that component will be taken from that component's own prior state, or from a prior call to that component's state setter.

    At the moment that you do

    const [letters, setLetter] = useState(props.letters.dos);
    

    you're splitting up your app's state into two completely separate parts; one with someVar in the parent component, and another with letters in the First child component. Further changes to either one of those states will not affect the other.

    To avoid this sort of problem, and to keep your data organized, it's usually a good idea to not duplicate state in more than one place. If you want a child component to be able to access and manipulate state linked to the parent, have the parent pass down the state, and if needed, the state setter, without putting another useState in the child.