Refactoring useState() To useReducer()
Tags: react, reduce, webdev, javascript
Published: 2020-04-08 00:10:26

---
title: Refactoring useState() To useReducer()
template: "post"
published: true
tags: React, Reduce, Web Dev, JavaScript
description: This article walks through how to combine multiple useState() React hooks logic into a single useReducer().
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/ppegg2n0j81voaswggfv.png
canonical_url: https://www.aboutmonica.com/blog/2020-04-04-refactoring-use-state-to-use-reducer
---

This article was originally published on [www.aboutmonica.com](https://www.aboutmonica.com/blog/2020-04-04-refactoring-use-state-to-use-reducer).

I recently created a [Stimulus Check Calculator](https://stimulus-check-calculator.now.sh/) based on figures from the CARES Act and the Washington Post to help people estimate the amount of their stimulus check under the CARES Act.
This article will walk through how I refactored the calculator's state management by consolidating multiple `useState()` React hooks into a single `useReducer()`. `useReducer()` is an alternative that can be considered when using `useState()` to manage state in functional React components. This article assumes some familiarity with [state management in React](https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class) and [React Hooks](https://reactjs.org/docs/hooks-reference.html).



![screenshot of the stimulus check calculator app](https://dev-to-uploads.s3.amazonaws.com/i/ppegg2n0j81voaswggfv.png)
Screenshot of the [Stimulus Check Calculator](https://stimulus-check-calculator.now.sh/).

Overview:
- [Managing form state with useState();](#managing-form-state-with-usestate)
  - [Setting the initial state with useState()](#setting-the-initial-state-with-usestate)
  - [Updating form state with useState()](#updating-form-state-with-usestate)
- [Managing form state with useReducer();](#managing-form-state-with-usereducer)
  - [Setting the initial state with useReducer()](#setting-the-initial-state-with-usereducer)
  - [Updating form state with useReducer()](#updating-form-state-with-usereducer)
- [Full Source Code of Examples](#full-source-code-of-examples)
  - [source code using useState():](#source-code-using-usestate)
  - [source code using useReducer():](#source-code-using-usereducer)


## Managing form state with useState();

In order to use the `useState()` React hook for state management of the calculator I first needed to import `useState` from React.

```js
import { useState } from "react";
```

### Setting the initial state with useState()

Then within the function that is returning the `Form` component I setup the `useState()` hooks for `taxYear`, `filingStatus`,`income`, `children` and `stimulusAmount`.

```js
  const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;
  const [taxYear, setTaxYear] = useState(2019);
  const [filingStatus, setFilingStatus] = useState(SINGLE);
  const [income, setIncome] = useState("75000");
  const [children, setChildren] = useState(0);
  const [stimulusAmount, setStimulusAmount] = useState(-1);
  ```
The parameter passed into `useState()` represents the default value for that particular state. Meaning that the below line is setting the default value of `taxYear` in state to `2019`.

```js 
const [taxYear, setTaxYear] = useState(2019);
```
### Updating form state with useState()
Event handlers, such as `onChange` or `onClick` can be used to update the component's state when data when a form changes. Managing form state by updating the component's internal state is considered a "[controlled component](https://reactjs.org/docs/uncontrolled-components.html)" vs. having the DOM manage the state of the form.

 In order to update the `taxYear`'s value to the selected year, there's an `onClick` event handler that calls `setTaxYear(year)` with the `year` parameter being the current `year` that is selected.

```js
 {[2019, 2018].map(year => (
            <button
              onClick={() => setTaxYear(year)}
              className={year == taxYear ? "selectedButton" : ""}
              key={year}
              name="tax-year"
            >
              {year == 2019 ? "Yes" : "No"}
            </button>
          ))}
```

 Similar logic is used to update `filingStatus` `income` and `children`, `stimulusAmount` and `handleSubmit` when form data is updated or submitted.

 ## Managing form state with useReducer();

In order to use the `useReducer()` React hook for state management of the calculator I first needed to import `useReducer` from React. If you are not familiar with `reducers` in JavaScript check out my article on [Understanding Reduce in Javascript](https://www.aboutmonica.com/blog/2020-03-29-understanding-reduce-in-javascript)

```js
import { useReducer } from "react";
```

### Setting the initial state with useReducer()

Then I set the initial state for the component like:
```js
const initialState = {
    taxYear: 2019,
    filingStatus: SINGLE,
    income: "75000",
    children: 0,
    stimulusAmount: -1,
  };

  const [state, dispatch] = useReducer(reducer, initialState);
  ```
Similar to `useState`, `useReducer` returns the related state along with a method to update the state. With `useReducer` instead of updating the state by passing a value to `setState()` an action should be dispatched which will call the `reducer`. 

In my case the `reducer` function looked like:
  ```js
  function reducer(state, action) {
  const { type, payload } = action;
  return { ...state, [type]: payload };
}
```
### Updating form state with useReducer()
Each time `dispatch` is called it should be called with an `action` item that contains a `type` and in this particular case a `payload` as well. The state of tax year can be updated `onClick` by firing 
```js
 onClick={() => dispatch({ type: "taxYear", payload: year })}
 ```
  instead of 
  ```js
    onClick={() => setTaxYear(year)}
```

`reducer(state, action)` is expecting to receive an `action` that is an object with `type` and `payload`. Within the reducer function the action's `type` and `payload` are used to return the current `state` with the `[type]: payload` overwritten. 

```js  
  const { type, payload } = action;
  return { ...state, [type]: payload };
```

In the case of updating the state from 2017 if the current state was:

```js
const initialState = {
    taxYear: 2019,
    filingStatus: SINGLE,
    income: "75000",
    children: 0,
    stimulusAmount: -1,
  };
  ```
  Then firing `onClick={() => dispatch({ type: "taxYear", payload: 2018 })}` would result in the reducer returning the current state but with only the value of `taxYear` overwritten and set to 2018. Note: this works as written becasue for each action in this example the `type` of action is the same as its corresponding key value in `state`.

## Full Source Code of Examples 
The full source code below compares the full implementations of the state management methods above. As was illustrated above,  `useReducer()` is another React hook that can be used for state management and can be implemented in a way that allows logic from `useState()` hooks to be consolidated. The related source code for the current version of the calculator is available on [GitHub](https://github.com/M0nica/stimulus-check-calculator).  

### source code using useState():
```js

import { filingStatuses } from "../utils/constants";
import { getStimulusAmount } from "../utils/calculateStimulus";
import { useState } from "react";



function Form() {
  const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;
  const [taxYear, setTaxYear] = useState(2019);
  const [filingStatus, setFilingStatus] = useState(SINGLE);
  const [income, setIncome] = useState("75000");
  const [children, setChildren] = useState(0);
  const [stimulusAmount, setStimulusAmount] = useState(-1);

  function handleSubmit(e) {
    e.preventDefault();
    setStimulusAmount(calculateStimulus(income, filingStatus, children));
  }

  return (
        <form onSubmit={handleSubmit}>
          <label htmlFor="tax-year">Have you filed your 2019 taxes yet?</label>
          {[2019, 2018].map(year => (
            <button
              onClick={() => setTaxYear(year)}
              className={year == taxYear ? "selectedButton" : ""}
              key={year}
              name="tax-year"
            >
              {year == 2019 ? "Yes" : "No"}
            </button>
          ))}
          <label htmlFor="filing-status">
            What was your filing status in your {taxYear} taxes?{" "}
          </label>
          {[SINGLE, MARRIED, HEADOFHOUSE].map(status => (
            <button
              onClick={() => setFilingStatus(status)}
              className={status == filingStatus ? "selectedButton" : ""}
              name="filing-status"
              key={status}
            >
              {" "}
              {status}
            </button>
          ))}
          <br />
          <label htmlFor="adjusted-income">
            What was your adjusted gross income in {taxYear}?
          </label>
          ${" "}
          <input
            type="number"
            inputMode="numeric"
            pattern="[0-9]*"
            value={income}
            onChange={e => setIncome(e.target.value)}
            min={0}
            name="adjusted-income"
          />
          <br />
          <label htmlFor="children">
            How many children under age 17 did you claim as dependents in{" "}
            {taxYear}?
          </label>
          <input
            type="number"
            inputMode="numeric"
            pattern="[0-9]*"
            value={children}
            onChange={e => setChildren(e.target.value)}
            min={0}
            name="label"
          />
          <br />
          <button type="submit" className="calculateButton">
            Calculate
          </button>
          <p>
            {" "}
            {stimulusAmount >= 0 &&
              (stimulusAmount > 0
                ? `Your stimulus amount is expected to be $${stimulusAmount}.`
                : `You are not expected to receive a stimulus.`)}
          </p>
          <br />
        </form>
  );
}

export default Form;
```


### source code using useReducer():
```js
import { useReducer } from "react";
import { filingStatuses } from "../utils/constants";
import { getStimulusAmount } from "../utils/calculateStimulus";

function reducer(state, action) {
  const { type, payload } = action;
  return { ...state, [type]: payload };
}

function Form() {
  const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;

  const initialState = {
    taxYear: 2019,
    filingStatus: SINGLE,
    income: "75000",
    children: 0,
    stimulusAmount: -1,
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: "stimulusAmount",
      payload: getStimulusAmount(income, filingStatus, children),
    });
  }

  const { taxYear, filingStatus, income, children, stimulusAmount } = state;

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="tax-year">Have you filed your 2019 taxes yet?</label>
      {[2019, 2018].map((year) => (
        <button
          onClick={() => dispatch({ type: "taxYear", payload: year })}
          className={year == taxYear ? "selectedButton" : ""}
          key={year}
          name="tax-year"
        >
          {year == 2019 ? "Yes" : "No"}
        </button>
      ))}
      <label htmlFor="filing-status">
        What was your filing status in your {taxYear} taxes?{" "}
      </label>
      {[SINGLE, MARRIED, HEADOFHOUSE].map((status) => (
        <button
          onClick={() => dispatch({ type: "filingStatus", payload: status })}
          className={status == filingStatus ? "selectedButton" : ""}
          name="filing-status"
          key={status}
        >
          {" "}
          {status}
        </button>
      ))}
      <br />
      <label htmlFor="adjusted-income">
        What was your adjusted gross income in {taxYear}?
      </label>
      ${" "}
      <input
        type="string"
        inputMode="numeric"
        pattern="[0-9]*"
        value={income}
        onChange={(e) => dispatch({ type: "income", payload: e.target.value })}
        min={0}
      />
      <br />
      <label htmlFor="children">
        How many children under age 17 did you claim as dependents in {taxYear}?
      </label>
      <input
        type="number"
        inputMode="numeric"
        pattern="[0-9]*"
        value={children}
        onChange={(e) =>
          dispatch({ type: "children", payload: e.target.value })
        }
        min={0}
        name="label"
      />
      <br />
      <button type="submit" className="calculateButton">
        Calculate
      </button>
      <p>
        {" "}
        {stimulusAmount >= 0 &&
          (stimulusAmount > 0
            ? `Your stimulus amount is likely to be ${new Intl.NumberFormat(
                "en-US",
                { style: "currency", currency: "USD" }
              ).format(stimulusAmount)}.`
            : `You are not expected to receive a stimulus.`)}
      </p>
      <br />
    </form>
  );
}

export default Form;
```