Chaining State Update in ReactJS Efficiently
Tags: react, webdev, frontend, javascript
Published: 2023-03-11 15:05:11

Managing states in ReactJS could be tricky. Changing state could trigger components or pages re-render. If it's not managed well, components re-render could lead to performance issues.

It is not uncommon to see a component that has dozens state. Some states are changed whenever other state changed. Also those state could make other states changed and so on. Fortunately, ReactJS has several utilities that could cover something like this. I will explain in a simple real use case how I approach this situation.

## Use Case

Let's suppose there's form with 2 inputs as shown in the image below,


![Use Case Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tplrptv1qe0vgru9dc6i.png)


By they way, I'm gonna implement the view by using __React Typescript__ and __Material UI__. Typescript would be our best friend when building large scale application because of its type safety. Material UI would give us nice-looking theme while also giving us a lot of flexibility.

The first input is weight type that consists of two different options, Kg and Pound. Second input is number input for weight value. The second input has additional property that shows value unit in the end of the input. I'm gonna name this property _adornment_ since Material UI also give the same name for this type of property.

The adornment value depends on first input. If user choose __Kg__ for the value, weight value input adornment will show _kg_. Also for __Pound__ value the adornment will show _lb_.

Our base view will be implemented like this,

```tsx
import React from 'react'
import {
  Button,
  FormControl,
  FormControlLabel,
  FormLabel,
  InputAdornment,
  Radio,
  RadioGroup,
  TextField,
  Typography
} from '@mui/material'

function OrderForm() {
  return (
    <>
      <Typography variant="h2" fontWeight={600} fontSize={24} sx={{ mb: 2 }}>
        Order Form
      </Typography>
      <form
        style={{ width: '80%', maxWidth: '1000px', display: 'block', margin: 'auto' }}
      >
        <FormControl>
          <FormLabel id="payloadType-input" sx={{ textAlign: 'start' }}>
            Payload Type
          </FormLabel>
          <RadioGroup
            row
            aria-labelledby="payloadType-input"
            name="payloadType"
          >
            <FormControlLabel value="kg" control={<Radio />} label="Kg" />
            <FormControlLabel value="pound" control={<Radio />} label="Pound" />
          </RadioGroup>
          <FormLabel id="payloadWeight-input" sx={{ textAlign: 'start', mt: 1 }}>
            Weight
          </FormLabel>
          <TextField
            aria-labelledby="payloadWeight-input"
            name="payloadWeight"
            size='small'
            type="number"
            fullWidth
            InputProps={{
              endAdornment: <InputAdornment sx={{ width: 12 }} position="end">kg</InputAdornment>,
            }}
          />
          <Button type="submit" variant="contained" sx={{ mt: 3 }}>Submit</Button>
        </FormControl>
      </form>
    </>
  )
}

export default OrderForm

```
Next let's implement the states.

## Naive Approach

If we look into the problem at a glance, there are three states that we gonna maintain. The first and second state will be the input values. The third state will be the input adornment. It makes sense making it as a state since we need to re-render the screen whenever the adornment change. Our code will be something like this.

```tsx
// ...

function OrderForm() {
  // States
  const [payloadType, setPayloadType] = React.useState<string>('kg')
  const [payloadWeight, setPayloadWeight] = React.useState<string>('0')
  const [adornmentLabel, setAdornmentLabel] = React.useState<string>('kg')

  return (
    // ...  
  )
}

export default OrderForm

```

Our inputs will be controlled by `payloadType` and `weight` state. I will be using conventional input state binding for this example.

```tsx

// ...

function OrderForm() {
  // States
  const [payloadType, setPayloadType] = React.useState<string>('kg')
  const [payloadWeight, setPayloadWeight] = React.useState<string>('0')
  const [adornmentLabel, setAdornmentLabel] = React.useState<string>('kg')

  React.useEffect(() => {
    // Assign new adornment
    const newAdornment = payloadType === 'pound' ? 'lb' : 'kg'
    setAdornmentLabel(newAdornment)
  }, [payloadType, setAdornmentLabel])

  return (
    <>
      <Typography variant="h2" fontWeight={600} fontSize={24} sx={{ mb: 2 }}>
        Order Form
      </Typography>
      <form
        style={{ width: '80%', maxWidth: '1000px', display: 'block', margin: 'auto' }}
      >
        <FormControl>
          <FormLabel id="payloadType-input" sx={{ textAlign: 'start' }}>
            Payload Type
          </FormLabel>
          <RadioGroup
            row
            aria-labelledby="payloadType-input"
            name="payloadType"
            // Input state binding
            onChange={(e) => setPayloadType(e.target.value)}
            value={payloadType}
          >
            <FormControlLabel value="kg" control={<Radio />} label="Kg" />
            <FormControlLabel value="pound" control={<Radio />} label="Pound" />
          </RadioGroup>
          <FormLabel id="payloadWeight-input" sx={{ textAlign: 'start', mt: 1 }}>
            Weight
          </FormLabel>
          <TextField
            aria-labelledby="payloadWeight-input"
            name="payloadWeight"
            size='small'
            type="number"
            fullWidth
            // Input state binding
            value={payloadWeight}
            onChange={(e) => setPayloadWeight(e.target.value)}
            // Replace with state
            InputProps={{
              endAdornment: <InputAdornment sx={{ width: 12 }} position="end">{adornmentLabel}</InputAdornment>,
            }}
          />
          <Button type="submit" variant="contained" sx={{ mt: 3 }}>Submit</Button>
        </FormControl>
      </form>
    </>
  )
}

// ...

```

Whenever our `payloadType` value changes, our input adornment should be changed based on `payloadType` value. In ReactJS, in order to do something whenever some values change, typically we can use `useEffect`. The `useEffect` will receive `payloadType` state in its dependency array since we want to do something with input adornment whenever `payloadType` change.

Let's try our implementation.


![Use Case Form after implementing the states](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vox5nslf4d21jzufgii.gif)


Yay, it works! But, does it?

## The Problem

Surely it works perfectly. But let me show you something.

I'm gonna put a counter that's increased whenever the screen re-rendered. I will also log the counter in the console.

```tsx

// ...

let counter = 0

function OrderForm() {
  // States
  const [payloadType, setPayloadType] = React.useState<string>('kg')
  const [payloadWeight, setPayloadWeight] = React.useState<string>('0')
  const [adornmentLabel, setAdornmentLabel] = React.useState<string>('kg')
  
  // Print and increase the value
  console.log('Screen is refreshed ', ++counter, ' times')

  // ...

```

I will refresh the page once and change the `payloadType` value. Take a look at the logging result.


![Re-render count in our Use Case Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7xmpq7js1mov7avxv22.gif)


Initially, the component is mounted for the first time so the console shows __1__ value. After that, I modify `payloadType` value. The console is showing __2__ and followed by __3__. Logically, the counter should only be increased 1 because I only change `payloadType` value once. What's happened?

> _Ah I see, the value is increased two times because there are two states change. The payload type input and then the input adornment._

Sure, that's the answer. But is it the best way we can do?

Doing sort kind of thing could lead into problem. For now it's fine because there are only two inputs and three states we maintain. But as our application grow, keeping this habit could lead into performance issue.

I did this once in my previous work. First there were only like five inputs and several states. Some of those states were related to input adornment and other supporting states that were updated whenever other states update.

As time went on, the requirements grew, and I still kept doing this habit. There were 15 inputs in total with their corresponding states and other states. There was notable input lag whenever user typed into field. Interacting with the form became very painful because of this.

There are some solutions for the problem, especially dealing with large form. But I will keep this topic for later. Right now I'm gonna focus on how do we chaining those states update so that we could minimize component re-render. So how do we tackle this problem?

## Update States Synchronously

Forget about `useEffect`, forget about chaining states. Sometimes simplest way is the best way. Instead of update the adornment inside `useEffect`, why don't we update the adornment directly after updating the `payloadType`? In order to do this we have to move our `payloadType` `onChange` handler into separate function.

```tsx

  // ...

  const payloadTypeChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setPayloadType(e.target.value)
  }

  return (
    <>
      <Typography variant="h2" fontWeight={600} fontSize={24} sx={{ mb: 2 }}>
        Order Form
      </Typography>
      <form onSubmit={submitHandler} style={{ width: '80%', maxWidth: '1000px', display: 'block', margin: 'auto' }}>
        <FormControl>
          <FormLabel id="payloadType-input" sx={{ textAlign: 'start' }}>
            Payload Type
          </FormLabel>
          <RadioGroup
            row
            aria-labelledby="payloadType-input"
            name="payloadType"
            // Move this
            onChange={payloadTypeChangeHandler}
            value={payloadType}
          >

  // ...

```

After that we can also update input adornment inside the same function.

```tsx

// ...

  const payloadTypeChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setPayloadType(e.target.value)

    const newAdornment = payloadType === 'pound' ? 'lb' : 'kg'
    setAdornmentLabel(newAdornment)
  }

// ...

```

Finally we can remove our `useEffect`. Now our page would only by rendered once whenever our `payloadType` change.


![Re-render count in Use Case Form after optimization](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezx59j9b4zzr3rck07kp.gif)


## Using useMemo

I personally don't like the first approach. I think `onChange` function handler should only handle / control input change so that our handler won't be bloated. Whatever change that involve `payloadType` should stay away from our change handler.

We can achieve this by using `useMemo`. `useMemo` is a hook that save value as a result from _complex_ calculation. Take a look at this example.

```tsx
import React from 'react'
import {
  Button,
  FormControl,
  FormControlLabel,
  FormLabel,
  InputAdornment,
  Radio,
  RadioGroup,
  TextField,
  Typography
} from '@mui/material'

function OrderForm() {
  const [payloadType, setPayloadType] = React.useState<string>('kg')
  const [payloadWeight, setPayloadWeight] = React.useState<string>('0')
  // Take a look
  const adornmentLabel = payloadType === 'pound' ? 'lb' : 'kg'

  return (
    <>
      <Typography variant="h2" fontWeight={600} fontSize={24} sx={{ mb: 2 }}>
        Order Form
      </Typography>
      <form
        style={{ width: '80%', maxWidth: '1000px', display: 'block', margin: 'auto' }}
      >
        <FormControl>
          <FormLabel id="payloadType-input" sx={{ textAlign: 'start' }}>
            Payload Type
          </FormLabel>
          <RadioGroup
            row
            aria-labelledby="payloadType-input"
            name="payloadType"
            onChange={(e) => setPayloadType(e.target.value)}
            value={payloadType}
          >
            <FormControlLabel value="kg" control={<Radio />} label="Kg" />
            <FormControlLabel value="pound" control={<Radio />} label="Pound" />
          </RadioGroup>
          <FormLabel id="payloadWeight-input" sx={{ textAlign: 'start', mt: 1 }}>
            Weight
          </FormLabel>
          <TextField
            aria-labelledby="payloadWeight-input"
            name="payloadWeight"
            size='small'
            type="number"
            fullWidth
            value={payloadWeight}
            onChange={(e) => setPayloadWeight(e.target.value)}
            InputProps={{
              endAdornment: <InputAdornment sx={{ width: 12 }} position="end">{adornmentLabel}</InputAdornment>,
            }}
          />
          <Button type="submit" variant="contained" sx={{ mt: 3 }}>Submit</Button>
        </FormControl>
      </form>
    </>
  )
}

export default OrderForm

```

I like this approach more. The above code could result the same behavior as before with one re-render. We have omitted the `useState` and use standard variable to save the value derived from `payloadType`. But this approach is not very good because this could lead into performance issue.

By processing a variable directly inside component, for every re-render, the process is executed. ReactJS components always be rendered many times, especially in large scale application. If the process you do is heavy and the component re-rendered many times, than we gonna have serious performance issue.

In order to limit processing variable, we can use `useMemo` hook. The hook can prevent the processing from happening by saving the result. `useMemo` also receives dependency array. The dependency array will make sure the process is executed only if array elements are changed.

We can implement it like this,

```tsx

// ...

function OrderForm() {
  const [payloadType, setPayloadType] = React.useState<string>('kg')
  const [payloadWeight, setPayloadWeight] = React.useState<string>('0')
  // Take a look
  const adornmentLabel = React.useMemo<string>(
    () => payloadType === 'pound' ? 'lb' : 'kg',
    [payloadType]
  )

// ...

```

`useMemo` receives two parameter. The first one is the function to process variable value and the second one is dependency array. By using `useMemo`, the `adornmentLabel` is changed only if `payloadType` is changed. We can put heavy process inside the function and it is only executed when elements inside dependency array are changed.

***

## Closing

So that's all from me. What do you think of this approach? Let me know your thoughts :D

If you like my article, make sure to click the ❤️ button.