Dont Depend On State From Callback Handlers in React
Tags: react, node, javascript, webdev
Published: 2020-04-21 13:59:30

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/preview_webform_v2.html?webforms_id=SM2hz)

## Thinking in React's Render Phase As Opposed to JavaScript's Execution Context

If you've been a react developer for awhile you probably might agree with me that working with state can _easily_ become the biggest pain in the rear of your day.

So here's a tip that might help keep you in check for introducing silent but catastrophic errors: _Avoid closures referencing state values from their callback handlers_.

If done right, you should have no problems dealing with state in callback handlers. But if you slip at one point and it introduces silent bugs that are hard to debug, that's when the consequences begin to engulf that extra time out of your day that you wish you could take back.

With that said, we're going to look at an issue in code that will show us a common problematic scenario when we work with state. The code example ahead will show a component `App`. It will declare a `collapsed` state (defaulted to `true`) and renders an `AppContent` component which renders the input element.

```jsx
function AppContent({ onChange }) {
  const [value, setValue] = React.useState('')

  function handleOnChange(e) {
    if (onChange) {
      onChange(function({ person, collapsed }) {
        console.log(collapsed)
        console.log(person)
        setValue(e.target.value)
      })
    }
  }

  return (
    <input placeholder="Your value" value={value} onChange={handleOnChange} />
  )
}

function App() {
  const [collapsed, setCollapsed] = React.useState(true)

  function onChange(callback) {
    const person = collapsed ? null : { name: 'Mike Gonzalez' }
    callback({ person, collapsed })
  }

  return (
    <div>
      <AppContent
        onChange={(cb) => {
          setCollapsed(false)
          onChange(cb)
        }}
      />
    </div>
  )
}
```

When a user types in something it will call its `onChange` handler from props which is directed to `App`. It will receive the `callback` argument and sets its `collapsed` state to `false` so that its children can expand to display their content. Then the execution ends up inside `handleOnChange` (the `callback`), passing in `collapsed` and a random `person` variable (Yes, random I know) that is populated with data _only if_ `collapsed` is `false`.

The code actually runs fine with no unexpected console errors, and life is well.

Actually, there's a _major_ issue in this code. The fact that we're thrown off with no console errors and that our code isn't breaking already makes it a dangerous bug!

Lets add some `console.log`s inside `handleOnChange` and see what we get:

```jsx
function handleOnChange(e) {
  if (onChange) {
    onChange(function({ person, collapsed }) {
      console.log(`collapsed: ${collapsed}`)
      console.log(`person: ${JSON.stringify(person)}`)
      setValue(e.target.value)
    })
  }
}
```

![handling-state-in-callback-handlers1](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/handling-state-in-callback-handlers1.jpg)

Wait a minute, why is `person` `null` and `collapsed` `true`? We already set the state value of `collapsed` to `false` and we know this is valid JavaScript code since the runtime was able to proceed without problems:

```jsx
return (
  <div>
    <AppContent
      onChange={(cb) => {
        setCollapsed(false)
        onChange(cb)
      }}
    />
  </div>
)
```

If you understand the execution context in JavaScript this makes no sense because the function that encapsulates the call to `setCollapsed` had finished _before_ sending the call to its local `onChange` function!

Well, that is actually still right. There's nothing JavaScript doing is wrong right now. It's actually _react_ doing its thing.

For a full explanation of the rendering process you can head over to their [documentation](https://reactjs.org/docs).

But, in short, basically at the time whenever react enters a new _render_ phase it takes a _"snapshot"_ of everything that is present _specific to that render phase_. It's a phase where react essentially creates a tree of react elements, which represents the tree _at that point in time_.

By definition the call to `setCollapsed` _does_ cause a re-render, but that render phase is at a future point in time! This is why `collapsed` is still `true` and `person` is `null` because the execution at that point in time _is specific to that render_, sorta like having their own little world that they live in.

This is how the concept of execution context looks like in JavaScript:

![javascript-execution-context-diagram-flow-chart](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/javascript-execution-context-diagram-flow-chart.jpg)

This is react's render phase in our examples (You can think of this as react having their _own execution context_):

![react-render-phase-reconciliation-flow-chart](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/react-render-phase-reconciliation-flow-chart.jpg)

With that said, let's take a look at our call to `setCollapsed` again:

![updating-state-in-render-phase-in-react](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/updating-state-in-render-phase-in-react.jpg)

![updating-state-in-render-phase-in-react2](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/updating-state-in-render-phase-in-react2.jpg)

This is all happening in the same render phase so that is why _collapsed_ is still `true` and `person` is being passed as `null`. When the entire component rerenders then the values in the next render phase will represent the values from the previous:

![react-state-update-reconciliation2](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/react-state-update-reconciliation2.jpg)

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/preview_webform_v2.html?webforms_id=SM2hz)