Update boolean state right with React Hooks
Tags: react, javascript, webdev, beginners
Published: 2020-05-11 14:31:21

---
title: Update boolean state right with React Hooks
published: true
description: 
tags: react, javascript, webdev, beginners
---
Recently I found a construction like this while doing code review:
```jsx
const MyComponent = (props) => {
  const [isToggled, setIsToggled] = React.useState(false);
  const toggle = React.useCallback(() => setIsToggled(!isToggled));

  return ...;
};
```
Creating a boolean state and a toggle method for it is a pretty common use case. The spinnet is 100% correct in terms of functionality. But it could be better in terms of performance. Let's see how it can be improved.

#So what's wrong?
First things first – `useCallback` does nothing in this implementation. Unless a dependencies array is passed as a second parameter, `useCallback` is not keeping the same reference to a callback through renders and is equal to the following callback declaration:
```javascript
const toggle = () => setIsToggled(!isToggled);
```
Also, current implementation violates the exhaustive dependencies rule: every value referenced inside the function should also appear in the dependencies array. This is required to ensure that values inside a callback are always up-to-date and avoid any bugs related to that.

Let's see in practice how these two impact performance. First, let's create a simple `RendersCounter` component that takes a single `onClick` prop. It's going to count how many times a component was rendered:
```jsx
import React from 'react';

const RendersCounter = ({ onClick }) => {
  const rendersCountRef = React.useRef(0);
  rendersCountRef.current += 1;
  
  return (
    <div>
      <span>
        RendersCounter rendered <b>{rendersCountRef.current}</b> time(s)
      </span>
      <button style={{ marginLeft: '10px' }} onClick={onClick}>
        toggle
      </button>
    </div>
  )
};

export default React.memo(RendersCounter);
```
Note that `RendersCounter` is wrapped with `React.memo`. The optimizations we're going to make are only working if a child component is a pure component: it's an instance of `React.PureComponent`, a functional component wrapped with `React.memo`, or has referential equality render optimization via `shouldComponentUpdate` or any other way to do it. If you don't have any of those implemented for a child component, it will be re-rendered every time a parent component is re-rendered regardless of the way you implement a callback.

Now let's use this component to see what happens if we don't pass dependencies to `useCallback` at all. I will create two separate state handlers: one for our boolean state and another one for storing a random number.
```jsx
const BasicBooleanState = () => {
  const [isToggled, setIsToggled] = React.useState(false);
  const toggle = React.useCallback(() => setIsToggled(!isToggled));

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
```

`RendersCounter` re-renders even though the boolean state is not changed at all!
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7lqwircsjwv7gtoser0c.gif)

As said before, the current `toggle` implementation with `useCallback` is equal to a regular arrow function declaration. It's re-created each render so `RendersCounter` gets a referentially different `onClick` prop that causes its re-render when it doesn't have to.

[Try it yourself](https://stackblitz.com/edit/react-boolean-hook-1?file=BasicBooleanState.js)

#Fixing missing dependencies
React documentation says:
> Every value referenced inside the function should also appear in the dependencies array

If you don't follow this rule you might end up having outdated values inside a callback. There are two external values used inside the `toggle` callback: `isToggled` and `setIsToggled`. Let's put them into the `useCallback`'s dependencies array.
```jsx
const BasicBooleanState = () => {
  const [isToggled, setIsToggled] = React.useState(false);

  // here we added [isToggled, setIsToggled] as a second parameter
  const toggle = React.useCallback(
    () => setIsToggled(!isToggled),
    [isToggled, setIsToggled],
  );

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
```

Now `RendersCounter` is not re-rendering when a random number changes! We said our callback to update only when `isToggled` or `setIsToggled` change so it's referentially equal unless `isToggled` changes.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5kum1rkdukqnml3qaqmh.gif)

But when we toggle the boolean state from the `RendersCounter` it gets re-rendered. And this makes sense because `isToggled` changes and it's a part the `useCallback`'s dependencies array.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ygjqoj2jrvy8drbsf3fn.gif)

[Try it yourself](https://stackblitz.com/edit/react-boolean-hook-2?file=BasicBooleanState.js)

#Optimizing a callback
To fix the problem of re-creating the `toggle` callback we need a way to avoid depending on `isToggled` directly but still have its actual value inside a callback. Here's what `useRef` can help with. We just need to create a reference once and update its value when `isToggled` changes. Then we replace `isToggled` with the reference in the dependencies array and callback itself and that's it!

Let's create a custom hook that would return a current boolean state and a toggle method that is changing a boolean value and never gets re-created
```jsx
// it might be a project-level reusable hook
const useToggle = (initialState) => {
  const [isToggled, setIsToggled] = React.useState(initialState);
  const isToggledRef = React.useRef(isToggled);

  // put [isToggledRef, setIsToggled] into the useCallback's dependencies array
  // these values never change so the calllback is not going to be ever re-created
  const toggle = React.useCallback(
    () => setIsToggled(!isToggledRef.current),
    [isToggledRef, setIsToggled],
  );

  // keep the value in isToggledRef actual
  // when isToggled changes, isToggledRef is updated accordingly
  React.useEffect(
    () => {
      isToggledRef.current = isToggled;
    },
    [isToggled],
  );

  return [isToggled, toggle];
}
```
Instead of `isToggled` we use `isToggledRef` to create the `toggle` callback. Both `isToggledRef` and `setIsToggled` are created only once and React ensures they never change and are referentially equal through renders. That means there's no reason for the `toggle` callback to be ever re-created.

To make sure the value in `isToggledRef` is up-to-date we use `useEffect` with a single `isToggled` dependency in the dependencies array. It will be executed only when `isToggled` changes.

It's time to use the hook we created:
```jsx
const OptimizedBooleanState = () => {
  const [isToggled, toggle] = useToggle(false);

  const [randomNumber, setRandomNumber] = React.useState(Math.random());
  const generateRandomNumber = React.useCallback(
    () => setRandomNumber(Math.random()),
    [],
  );

  return (
    <div>
      <div>
        Current random number is <b>{randomNumber}</b>
        <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}>
          regenerate
        </button>
      </div>
      <div>
        Boolean is set to <b>{String(isToggled)}</b>.
      </div>
      <RendersCounter onClick={toggle} />
    </div>
  );
}
```

Now `RenderCounter` never gets re-rendered!
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lgr14f0k2jq5u3f7d0kn.gif)

[Try it yourself](https://stackblitz.com/edit/react-boolean-hook-3?file=OptimizedBooleanState.js)

##Update
As [Juan Gabriel S. Palarpalar](https://dev.to/juangabrieldev) mentioned in comments, there's no need to use refs in this case. The desirable behavior can be achieved with a functional state updater. Instead of passing a value to `setIsToggled` we need to pass a function that takes the current state as the first argument. This really makes the hook way clearer:
```js
setIsToggled(state => !state);
```

Here's how updated `useToggle` hook looks:
```jsx
const useToggle = (initialState) => {
  const [isToggled, setIsToggled] = React.useState(initialState);

  // put [setIsToggled] into the useCallback's dependencies array
  // this value never changes so the callback is not going to be ever re-created
  const toggle = React.useCallback(
    () => setIsToggled(state => !state),
    [setIsToggled],
  );

  return [isToggled, toggle];
}
```

[Try it yourself](https://stackblitz.com/edit/react-boolean-hook-4?file=OptimizedBooleanState.js)

#Conclusion
At the end of the day, `useCallback` is only about optimization. Your code will still work properly if you declare a callback as a plain arrow function so it's up to you to find a balance between optimization and code brevity.

React Hooks API is super powerful. It allows you to write clear declarative code. It can also boost your app's performance if cooked right.

Thank you for reading!