Adding types to reducer functions

Things to look at when typing reducers

There are a couple of things that make typing reducer functions in react a little less error prone and give you some nice type checking and auto complete capabilities.

Among them are:

Using enums for…


This content originally appeared on DEV Community and was authored by Borislav Hadzhiev

Things to look at when typing reducers

There are a couple of things that make typing reducer functions in react a little less error prone and give you some nice type checking and auto complete capabilities.

Among them are:

  • Using enums for action types
  • Using unions for actions
  • Being explicit abut the reducer's return value
  • Throw an error for the default case - if you didn't handle it, most likely it wasn't intended (even though we are using typescript and we should never mistype - the default case is almost never what you want)

Example

Let's look at a simple copy-paste style example in a single file called App.tsx and go over some of the key points:

import {useEffect, useReducer} from 'react';

type UserAttrs = {
  gender?: string;
  name?: string;
  country?: string;
  email?: string;
};

type UserState = {
  loading: boolean;
  error: string | null;
  data: UserAttrs;
};

enum ActionType {
  FETCH_USER = 'fetch_user',
  FETCH_USER_SUCCESS = 'fetch_user_success',
  FETCH_USER_ERROR = 'fetch_user_error',
}

type Action =
  | {type: ActionType.FETCH_USER}
  | {
      type: ActionType.FETCH_USER_SUCCESS;
      payload: Required<UserAttrs>;
    }
  | {type: ActionType.FETCH_USER_ERROR; payload: string};

function reducer(_state: UserState, action: Action): UserState {
  switch (action.type) {
    case ActionType.FETCH_USER: {
      return {loading: true, error: null, data: {}};
    }
    case ActionType.FETCH_USER_SUCCESS: {
      return {loading: false, error: null, data: action.payload};
    }
    case ActionType.FETCH_USER_ERROR: {
      return {loading: false, error: action.payload, data: {}};
    }
    default: {
      throw new Error(`Unhandled action type - ${JSON.stringify(action)}`);
    }
  }
}

function App() {
  const [{loading, error, data}, dispatch] = useReducer(reducer, {
    loading: false,
    error: null,
    data: {},
  });

  useEffect(() => {
    const fetchUser = async () => {
      dispatch({type: ActionType.FETCH_USER});
      try {
        const res = await fetch('https://randomuser.me/api/');
        const parsed = (await res.json()) as {
          results: [
            {
              gender: string;
              name: {first: string};
              location: {country: string};
              email: string;
            },
          ];
        };
        const user = parsed.results[0];
        dispatch({
          type: ActionType.FETCH_USER_SUCCESS,
          payload: {
            gender: user.gender,
            name: user.name.first,
            country: user.location.country,
            email: user.email,
          },
        });
      } catch (error) {
        if (error instanceof Error) {
          dispatch({type: ActionType.FETCH_USER_ERROR, payload: error.message});
        } else {
          dispatch({
            type: ActionType.FETCH_USER_ERROR,
            payload: 'Something went wrong',
          });
        }
      }
    };

    fetchUser();
  }, []);

  return (
    <div>
      {loading && <h3>Loading...</h3>}
      {error && <h1 style={{color: 'red'}}>{error}</h1>}
      <h1>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The first thing to look at is using an enum for the ActionType variables. You don't want to be duplicating action type strings at the case statements, nor in the dispatch calls, you could easily mistype an action and you don't get the nice autocomplete from typescript.

For our Action type we use a union. Typing the action as a union allows us to use the case statement like a type guard. In other words - if the action type is equal to ActionType.FETCH_USER_ERROR - you must provide a payload parameter of type string, otherwise typescript will error out.

In the default case of our reducer we simply throw an error, the default case is almost never what you want - it better to be explicit - if we dispatched an action which wasn't handled we should know about it - it's
strange an not intuitive.

When dispatching actions always use the ActionType enum for auto completion and to avoid typos.

This post was originally published at bobbyhadz.com on February 26, 2021.


This content originally appeared on DEV Community and was authored by Borislav Hadzhiev


Print Share Comment Cite Upload Translate Updates
APA

Borislav Hadzhiev | Sciencx (2021-02-26T18:27:24+00:00) Adding types to reducer functions. Retrieved from https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/

MLA
" » Adding types to reducer functions." Borislav Hadzhiev | Sciencx - Friday February 26, 2021, https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/
HARVARD
Borislav Hadzhiev | Sciencx Friday February 26, 2021 » Adding types to reducer functions., viewed ,<https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/>
VANCOUVER
Borislav Hadzhiev | Sciencx - » Adding types to reducer functions. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/
CHICAGO
" » Adding types to reducer functions." Borislav Hadzhiev | Sciencx - Accessed . https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/
IEEE
" » Adding types to reducer functions." Borislav Hadzhiev | Sciencx [Online]. Available: https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/. [Accessed: ]
rf:citation
» Adding types to reducer functions | Borislav Hadzhiev | Sciencx | https://www.scien.cx/2021/02/26/adding-types-to-reducer-functions/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.