3

I have a functional React component that roughly looks like this:

const MyComponent = (prop1) => {
  
  const [myState, setState] = useState(prop1)  

  return <>
    {myState && <div>Some text...</div>}
    <div onClick={() => setState(true)}>Click me</div>
  </>
}

This obviously doesn't work because React only computes the initial state part once, so myState isn't updated on prop1 change.

Is there a way to make this work without using useReducer? Is this a good use case for useEffect?

internet
  • 303
  • 3
  • 19
  • 5
    Why even use `state`? A change in `prop1` will cause a rerender – chazsolo Aug 10 '21 at 16:38
  • 1
    Are you sure you need that pattern? https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions – ludwiguer Aug 10 '21 at 16:41
  • @chazsolo the actual code makes a computation based on prop and uses it as initialState. I need state cuz I need to explicitly "set the state" on a click. – internet Aug 10 '21 at 17:44
  • @ludwiguer thanks, I wasn't aware of that article. – internet Aug 10 '21 at 17:45
  • You can consider passing a function from the parent into the child that performs the same action (setting prop1 to true). In the end it's up to you, but that would hide the implementation details from your component. – chazsolo Aug 10 '21 at 17:47
  • @chazsolo yes, that's my concern. Earlier, I was passing a setState handler as a prop from parent and using that, but I had to handle it outside the component, which I didn't want – internet Aug 10 '21 at 17:50

3 Answers3

4

This is because state is only initialized once. That's why the second value returned from useState() is a setter function. It's intended to be the only way to update the state after initialization. In this case you would use a useEffect() hook to update the state when props change (although there's not generally a great reason to initialize state with a prop).

const MyComponent = (prop1) => {
  
  const [myState, setState] = useState(prop1)

  useEffect(() => {
    setState(prop1)
  }, [prop1])

  return <>
    {myState && <div>Some text...</div>}
    <div onClick={() => setState(true)}>Click me</div>
  </>
}
Keith Brewster
  • 3,302
  • 2
  • 21
  • 28
2

To update myState on prop1 change, pass a second argument to useEffect that is the array of values that the effect depends on.

An example looks like this:

const MyComponent = ({ prop1 }) => {
  const [myState, setState] = useState(prop1);

  useEffect(() => {
    setState(prop1);
  }, [prop1]);

  return (
    <>
      {myState && <div>Some text...</div>}
      <div onClick={() => setState(true)}>Click me</div>
    </>
  );
};
Jason Jin
  • 1,739
  • 14
  • 21
0

useState(initialValue) will only use initialValue on the first render and does not change after re-renders. So you need to write your code like this:

const MyComponent = (prop1) => {
  
  const [myState, setState] = useState(prop1)

  useEffect(() => {
    setState(prop1)
  }, [prop1])

  return <>
    {myState && <div>Some text...</div>}
    <div onClick={() => setState(true)}>Click me</div>
  </>
}
Sanket Shah
  • 2,888
  • 1
  • 11
  • 22