Toggle boolean state effectively in React

August 18, 2022

Introduction

If we want to preserve some state in the React application, we are using an useState hook. It is not bad, but is there any other option? Let's explore!

Let's create a custom hook that will return two values - the state and a function to update the state.

useState implementation

index.tsx
1import { useState } from 'react'
2
3type UseToggleReturnType = {
4 isToggled: boolean
5 toggle: () => void
6}
7
8export const useToggle = (): UseToggleReturnType => {
9 // We do not need to export the `setIsToggled` function, as we want to simplify the hook usage.
10 const [isToggled, setIsToggled] = useState(false)
11
12 // Instead of providing the `setIsToggled` function, we have a specialized function to toggle the current state for us
13 const toggle = () => {
14 setIsToggled(!isToggled)
15 }
16
17 return { isToggled, toggle }
18}

Tests

As you may know, I'm a big fan of testing, so let's implement some tests!

index.tsx
1import { act, renderHook } from '@testing-library/react'
2
3import { useToggle } from './use-toggle'
4
5// assert that state and function to toggle the state are returned
6describe('useToggle - return type', () => {
7 const { result } = renderHook(() => {
8 return useToggle()
9 })
10
11 it('should return an object with a `isToggled` property, which is false', () => {
12 expect(result.current).toHaveProperty('isToggled')
13 expect(result.current.isToggled).toBe(false)
14 })
15
16 it('should return an object with a `toggle` property, which is an function', () => {
17 expect(result.current).toHaveProperty('toggle')
18 expect(result.current.toggle).toBeInstanceOf(Function)
19 })
20})
21
22// verify if the state is updated when the toggle function is called
23describe('useToggle - toggling', () => {
24 it('should run the `toggle` function and check if `isToggled` is set to `true`', () => {
25 const { result } = renderHook(() => {
26 return useToggle()
27 })
28
29 expect(result.current.isToggled).toBe(false)
30
31 act(() => {
32 result.current.toggle()
33 })
34
35 expect(result.current.isToggled).toBe(true)
36 })
37})

useReducer implementation

As we have tests already implemented, we can use the useReducer hook to implement the same functionality.

Why useReducer? Because it is a more powerful version of useState, which allows us to use a reducer function to update the state based on the previous state.

index.tsx
1import { useReducer } from 'react'
2
3type UseToggleReturnType = {
4 isToggled: boolean
5 toggle: () => void
6}
7
8export const useToggle = (): UseToggleReturnType => {
9 // When calling a `toggle` function, a callback passed to the `useReducer` will be called with the previous state, returning the opposite state.
10 const [isToggled, toggle] = useReducer((previousValue) => {
11 return !previousValue
12 // `false` is the initial state
13 }, false)
14
15 return { isToggled, toggle }
16}

Tests are still passing, and now we can use the useReducer hook to implement the same, but with a more robust approach.

Conclusion

The aim of this article was not to show shorter code or to create some library but to show how to use the useReducer hook instead of the useState hook for managing the state that should be toggleable.

Have you found any bugs, or do you have any questions? Feel free to reach out on Twitter.

If you do not want to miss the next article, click on the follow button!

Share this post on Twitter