65

I'm refactoring to use Hooks and I've hit a very confusing wall

I have a basic functional component like so:

export const MakeComponent = props => {
  const { path, value, info, update } = props;

  const [val, setVal] = useState(value);
  console.log(value, val); // abc undefined

 return (...)
}

The log returns abc undefined - i.e. value in props is definitely defined, but the first argument returned from useState(value) is undefined

Just to test that hooks were working at all, I tried useState("abc") and that logs abc as expected.

I have no idea what I'm doing wrong - any ideas?

React version: 16.8.6

EDIT here is the parent component - nothing fancy going on here as far as I can see!

<MakeComponent
  path={key}
  value={item[key]}
  info={value}
  update={updateDiffs}
/>
14
  • 1
    How are you calling MakeComponent?
    – Dupocas
    Commented Nov 12, 2019 at 12:28
  • 2
    Calling a component like a function is invalid. But I meant {...props} how it looks like? Could you post code for the parent component?
    – Dupocas
    Commented Nov 12, 2019 at 12:34
  • 1
    make sure that 'value' received form props is undefined or not. if possible do share some good code snippet Commented Nov 12, 2019 at 12:35
  • 1
    Ok parent but as a result of async operation? If it is not too complicated you can create a sandbox.
    – devserkan
    Commented Nov 12, 2019 at 12:51
  • 3
    I think the value prop is initially undefined and updated later on, so the state is initialized as undefined
    – abidibo
    Commented Nov 12, 2019 at 12:59

3 Answers 3

127

As it is alluded to in the comments, useState(initialState) (or whatever you call it) will only use initialState on the first render.

The value you want the state to be initially. It can be a value of any type, but there is a special behavior for functions. This argument is ignored after the initial render.

(React Docs, emphasis mine)

After this, on re-renders the useState function does not change the state based on new changes to the props passed in.

To make changes reflected everytime value changes, register an effect on the input prop like so

export const MakeComponent = props => {
  const { path, value, info, update } = props;

  const [val, setVal] = useState(value);
  useEffect(() => { setVal(value)}, [value] )

 return (...)
}

Note that just setting state based on the props changing is a bit of an anti-pattern, as MonsterBucket notices you could just rely directly on the props changing to trigger a re-render:

export const MakeComponent = props => {
  const { path, value, info, update } = props;

  const [val, setVal] = useState(value);
  if (val !== value) { // don't update unnecessarily
    setVal(value);
  }

 return (...)
}

And instead reserve useEffect for side effects, mostly those outside of the React render cycle.

To see examples of these, have a look as these ReactJs docs - You might not need an effect, which covers lots of other examples.

2
  • first answer makes an infinite loop and the second answer causes the "Too many re-renders" Error
    – Ali
    Commented Oct 24, 2023 at 10:26
  • @Ali, I'll need to see more of your code to help, so feel free to ask and link it here Commented Oct 24, 2023 at 10:57
9

Here you have to add useEffect if you want to update the state on props change, which will listen to prop change & will update the state value accordingly

here is the updated snippet

export const MakeComponent = props => {
     const { path, value, info, update } = props;
        
     const [val, setVal] = useState(value);
     useEffect(() => { setVal(value)}, [value] )
     return (<div>{val}</div>)
}

Attching sandbox link

https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js

2
  • this gave me a return of { object Object } and debugger shows val = {value: undefined} Commented Jan 29, 2021 at 19:45
  • @idanicoleherbert Added the codesandbox demo, you can compare your changes with that & update Commented Jan 30, 2021 at 13:44
8

By the time you pass the prop value to useState the value of it can be yet to set. value itself might have been undefined yet.

Also setState is not truly sync so if the useState uses same mechanism as setState your state val might not be set to value yet as the initial value.

In such cases, using props as the initial values to states, you should use a side effect with no dependency. Once the first render achieved the effect will run and set your state with prop. You can let the initial value of the component be undefined passing nothing with no problems.

export const MakeComponent = props => {
  const { path, value, info, update } = props;

  const [val, setVal] = useState();

  // get initial state after initial render
  useEffect(() => {
    setVal(value)
  }, [])
  console.log(value, val); // abc undefined then will log abc abc

 return (...)
}

Just keep in mind that props in React are meant to be read-only, state is for read and write. But it is perfectly fine, and no not an anti pattern, if you use a prop just as an initial value for a state and use the state you have set with the prop after that instead of the prop. That is for consistency since you might have two different values at a time from a prop and a state in circumstances.

Your case might need to care for the value of the prop at an arbitrary time depending on you needs as stressed in one of the above answers. You question does not state that. Still, if so, you can add the prop to the dependency array of the effect that sets the state with that prop, and yes write separate effects for other props you want the same, well, effect.

If you don't need writing for that data you do not need that pattern at all, just use prop and it will be up to date and you will have consistency. But you apparently do so I hope the pattern I suggest above works for you as it does for me.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.