Keeping React Hooks With UseState in Sync With Props

An annoying footgun with React hooks is using useState with an initial value from props. It might appear like useState will be called each render, but it doesn’t (the bad kind of magic). Instead, it will run once when the component is initially rendered and you will run into stale read bugs when you rely on props to re-render the component with updated data.

To workaround the issue, you need to use useEffect to update the stateful data.

function MyComponent(props) {
  const [user, setUser] = React.useState(props.user);

  // This is needed or you will get a stale read when props.user
  changes React.useEffect(() => {
    setUser(props.user);
  }, [props.user]);

  return (
    <h1>Ugh that's easy to forget {user.name}</h1>
  )
}

This is annoying because it breaks the mental model of React Hooks as “just functions”. Maybe that’s still true, but they’re more like “functions with a lifecycle and state that you can’t easily inspect”.

An alternative (found in this StackOverflow thread) is to wrap this behavior in a custom hook.

import {useEffect, useState} from 'react';

export function useStateWithDep(defaultValue: any) {
  const [value, setValue] = useState(defaultValue);

  useEffect(() => {
    setValue(defaultValue);
  }, [defaultValue]);

  return [value, setValue];
}

Then you could rewrite the original example like so:

function MyComponent(props) {
  const [user, setUser] = useStateWithDep(props.user);

  return (
    <h1>Yay no stale reads when props change {user.name}!</h1>
  )
}