126

I am trying to set the state using React hook setState() using the props the component receive. I've tried using the below code:

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

const Persons = (props) =>  {

    // console.log(props.name);

   const [nameState , setNameState] = useState(props)

   console.log(nameState.name);
   console.log(props.name);
 
   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;

The issue is the state is being set upon component being loaded. But when it receive new props, the state is not getting updated. How to update the state in this case?

starball
  • 20,030
  • 7
  • 43
  • 238
METALHEAD
  • 2,734
  • 3
  • 22
  • 37

8 Answers8

163

useState hooks function argument is being used only once and not everytime the prop changes. You must make use of useEffect hooks to implement what you would call the componentWillReceiveProps/getDerivedStateFromProps functionality

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

const Persons = (props) =>  {
   const [nameState , setNameState] = useState(props)

   useEffect(() => {
       setNameState(props);
   }, [props])

   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;
Taras Lukavyi
  • 1,339
  • 2
  • 14
  • 29
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 2
    There have been discussions that initializing state from props in an anti-pattern, but since we have hooks, I guess things are changed. But I am not sure. What do you think? – Imdadul Huq Naim May 08 '19 at 17:11
  • 2
    @ImdadulHuqNaim No the things still remain the same, only way of implementing them have changed. It depends whether you need to derive state from props or not. For instance there might be a case where the inputs initial value is provided by props but it still to change locally and update in parent on say submit. – Shubham Khatri May 09 '19 at 05:58
  • 20
    But doing this re-renders the component again right. This was not the case in getDerivedStateFromProps when we are syncing the props to state. – METALHEAD Jul 27 '19 at 03:18
  • 12
    Why do you use `props.name` and `props.age` etc. in the return, instead of `nameState.name` & `nameState.age` etc. ? – sutherlandahoy Aug 29 '19 at 07:49
  • 12
    This approach looks great, but doesn't this render component twice? Because we are using setState() in useEffect. Doesn't this hit performance? – METALHEAD Feb 04 '20 at 13:20
  • 25
    Wrong answer, this will result in a wasteful, ghost re-rendering. useEffect has no business being used here. The simplest way to do it is: https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops There are more advanced techniques applicable in some cases, like coding a useComputedState hook. Some other suggestions: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html This is a nasty by product of react hooks. – AlexG May 09 '20 at 23:52
  • @AlexG What you suggested also sets a state and causes a re-render. The docs state: `React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.` – supra28 Sep 29 '20 at 11:38
  • 3
    @supra28 Yes, but It won't actually touch the DOM, as it does in this answer. – AlexG Nov 04 '20 at 22:48
  • Pls check my answer without additional re-renders. – Alexander Danilov Jan 19 '22 at 13:54
  • This will tigger the useEffect everytime parent component of `Persons` renders – Shan Jan 19 '22 at 14:07
37

The props value in useState(props) is used only during the initial render, further state updates are done with the setter setNameState.

In addition, there is no need for useEffect when updating derived state:

const Person = props => {
  const [nameState, setNameState] = useState(props.name);
  // update derived state conditionally without useEffect
  if (props.name !== nameState) setNameState(props.name);
  // ... other render code
};

From React docs:

[...] you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.

[...] an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.

In essence, we can optimize performance by getting rid of an additional browser repaint phase, as useEffect always runs after the render is committed to the screen.

Working example

This is a contrived example illustrating above pattern - in real code you would read props.name directly. See the React blog post for more appropriate derived state use cases.

const Person = props => {
  const [nameState, setNameState] = React.useState(props.name);
  // Here, we update derived state without useEffect
  if (props.name !== nameState) setNameState(props.name);

  return (
    <p>
      <h3>Person</h3>
      <div>{nameState} (from derived state)</div>
      <div>{props.name} (from props)</div>
      <p>Note: Derived state is synchronized/contains same value as props.name</p>
    </p>
  );
};

const App = () => {
  const [personName, setPersonName] = React.useState("Lui");
  const changeName = () => setPersonName(personName === "Lukas" ? "Lui" : "Lukas");

  return (
    <div>
      <Person name={personName} />
      <button onClick={changeName}>Change props</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
ford04
  • 66,267
  • 20
  • 199
  • 171
  • 1
    But does this hold good if we are getting a whole new object as a prop and we need to sync it to the state. How can we do the deep serach inside an object to check what's changed and what's not? – METALHEAD May 21 '20 at 19:17
  • 1
    Not sure, what you are trying to do. Though you would have to write the deep comparison logic manually, like in above code: `if (props.name.first !== nameState.first)`. Or use a deep comparison helper – ford04 May 21 '20 at 20:00
  • I wish I have seen this answer in the morning... I have just spend a whole day on this problem. – aztack Mar 27 '21 at 16:11
17

This general idea can be put into hook:

export function useStateFromProp(initialValue) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => setValue(initialValue), [initialValue]);

  return [value, setValue];
}


function MyComponent({ value: initialValue }) {
  const [value, setValue] = useStateFromProp(initialValue);

  return (...);
}
brietsparks
  • 4,776
  • 8
  • 35
  • 69
  • 4
    For typescript, it's going to want this slightly awkwardly typed version: export function useStateFromProp(initialValue: T): [T, Dispatch>] { const [value, setValue] = useState(initialValue); useEffect(() => setValue(initialValue), [initialValue]); return [value, setValue]; } – chrismarx May 26 '21 at 12:29
  • about the good comment from @chrismarx just add: import { Dispatch, SetStateAction, useEffect, useState } from "react"; – ValRob Jul 15 '22 at 08:11
5

For that, you need to use the useEffect so your code looks like. As you want to avoid to re-render again if pros didn't change then you have to check first on useEffect and then set the props to current variable.

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

const Persons = props => {
  // console.log(props.name);

  const [nameState, setNameState] = useState(props);

  console.log(nameState.name);
  console.log(props.name);
  useEffect(
    () => {
      if (nameState !== props.name) {
        setNameState(props.name);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

Demo

Dhaval Patel
  • 7,471
  • 6
  • 37
  • 70
  • 3
    This approach looks great, but doesn't this render component twice? Because we are using setState() in useEffect. Doesn't this hit performance? – METALHEAD Feb 04 '20 at 13:21
  • 1
    @METALHEAD: it will not be called twice because we have also checked if nameState changed then only useEffect should execute, see the second argument of useEffect – Dhaval Patel Feb 05 '20 at 07:57
  • 5
    @DhavalPatel The prop changed triggered a render and the `setNameState()` triggers another one. Seems like twice to me. – Drazen Bjelovuk Mar 22 '20 at 23:42
1
import React, { useState, useEffect } from "react";

const Persons = props => {
  // console.log(props.name);

  const [nameState, setNameState] = useState(props);

  console.log(nameState.name);
  console.log(props.name);
  useEffect(
    () => {
      if (nameState !== props) {
        setNameState(props);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

As per the Hooks react document, all the time when any props is update or any update in component is there then useEffect will be called. So you need to check the condition before updating useState and then update your value so that it continuously doesn't do re-rendering

Kajol Chaudhary
  • 257
  • 2
  • 9
1

If you need to calculate state from props and other state without additional re-renders, consider:

a) Using useMemo hook.

const Component = ({ name }) => {
  const [surname, setSurname] = useState('');

  const fullName = useMemo(() => {
     return name + ' ' + surname;
  }, [name, surname])

  ...
}

b) Calcuating inside render if it is not very heavy:

const Component = ({ name }) => {
  const [surname, setSurname] = useState('');

  const fullName = name + ' ' + surname;

  ...
}

c) For hard cases where you need to compare prev props and should be able to update state from another place you can use refs, though it doesn't look good:

const Component = ({ name }) => {
  const prevNameRef = useRef()
  const derivedState = useRef();

  if (prevNameRef.current !== name) {
    derivedState.current = ...
    prevNameRef.current = name;
  }
 
  // some other place
  derivedState.current = ...
}
Alexander Danilov
  • 3,038
  • 1
  • 30
  • 35
0

I believe the problem indicates an attempt to use one conceptual variable or set of variables to do two different things. For example trying to get props.name and name to do the same thing.

So if

const [name, setName] = useState(props.name)

isn't enough and you find yourself trying to force props.name into state variable name later in the function then maybe name is being overloaded. Try setting up another state variable - eg. updatedName and see if things work better.

The original example doesn't demonstrate this problem since the state variables are never used except in log statements.

If const [name, setName] = useState(props.name) updated on ever re-render there would be no point in having state variable name since it would always be the same as props.name (and further attempts to change it would cause re-render).

Ross Attrill
  • 2,594
  • 1
  • 22
  • 31
  • I believe that my answer is useful - particularly in that it points out that there is no use case in the question since the state variables are only used in logging. I would love to know why this was voted down. – Ross Attrill Jun 23 '21 at 22:12
0

I figured an alternative solution avoiding useEffect. Instead it uses two useState's. I put it into a custom hook for you:

export function useStateFromProp(propValue) {
  const [value,          setValue         ] = useState(propValue);
  const [propValueState, setPropValueState] = useState(propValue);

  if (propValueState != propValue) {
    setPropValueState(propValue);
    setValue(propValue);
  }

  return [value, setValue];
}


function MyComponent({ value: propValue }) {
  const [value, setValue] = useStateFromProp(propValue);

  return (...);
}

The main benefit would be that now the re-render that was normally triggered by the useEffect happens before any child components are re-rendered. so this would be faster.

Disclaimer: I did not test this yet. I did some googling and found this supporting article: https://pretagteam.com/question/in-react-hooks-when-calling-setstate-directly-during-render-is-the-rerender-guaranteed-to-run-before-the-render-of-children

MrRom
  • 1