jamiebuilds / unstated-next

200 bytes to never think about React state management libraries ever again

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Multiple calls in same rendering cycle references old state

ypresto opened this issue · comments

function useCounter(initialState = 0) {
  let [count, setCount] = useState(initialState)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

then

counter.increment()
counter.increment()

=> counter.count === 1

For example, this could be caused by immediate error callback of API client.

function useStore(initialState = 0) {
  const [state, setState] = useState(initialState)
  const onInput = (input) => setState({ ...state, input })
  const onRequestStart = () => setState({ ...state, loading: true, sentInput: state.input })
  const onRequestFailed = () => setState({ ...state, loading: false })
  return { state, decrement, increment }
}

const [state, actions] = Store.useContainer()
actions.onRequestStart()
api.call(params, (data, error) => {
  if (error) actions.onRequestFailed() // could reference old state value, "sentInput" value will be lost.
})

Maybe related: #26

This is standard react hooks behavior — your count variable is closed-over when creating your decrement behavior. In this case you probably want to use the reducer form of setState or just use useReducer directly.

You'll also want to wrap those callbacks in useCallback to avoid unnecessary re-renders.

function useCounter(initialState = 0) {
  const [count, setCount] = useState(initialState)
  const decrement = useCallback(() => setCount(count => count + 1), [])
  const increment = useCallback(() => setCount(count => count + 1), [])

  return { count, decrement, increment }
}

The answer by @shrugs should be added to the official documentation. I think there are also cases where decrement and increment should just have their own context, without count. That's similar to how useReducer is recommended with context.

https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down