Exploring Redux-Persist with Pokémon!
Tags: react, redux, typescript, webdev
Published: 2022-10-20 13:57:45

### Disclaimer

*This post is intended for developers with a basic understanding of using Redux and its concepts such as stores, actions and reducers. If you are unfamiliar with these concepts, I highly recommend starting with the [Redux Essentials](https://redux.js.org/tutorials/essentials/part-1-overview-concepts) documentation or a concise [Redux for Beginners Tutorial video](https://www.youtube.com/watch?v=CVpUuw9XSjY&t=985s&ab_channel=developedbyed), if you’re looking for a sample project walkthrough.*

## Simple Recap on Redux

Redux is an open-source JS library designed to help developers manage the state of variables in their applications. Redux does this by creating a centralized store, and creates processes such as dispatch and reducers. This ensures that the various components from any part of a complex application, can read and update the data from the same store, in a uniform and consistent manner. Hence, this allows easy communication between components in an application.

In this blog post, I will not be going in-depth into the boilerplate setup of Redux and other miscellaneous steps in the projects (writing different React components or CSS). I will instead start by showing a simple application that uses Redux, and explain how Redux-Persist will tackle the pain point of the application.

## Pokémon with Redux

### Setting up the Project

 I will be showing a simple Next.js application, initialized with typescript. Additionally, we will be using 2 Redux libraries:

1. react-redux


The library needed to have the core redux functionalities - Store, dispatching, actions and so on. Most of us should already be familiar with this.
2. @reduxjs/toolkit


A [toolkit](https://redux.js.org/redux-toolkit/overview#:~:text=Redux%20Toolkit%20makes%20it%20easier,of%20skill%20level%20or%20experience.) makes it easier to write good Redux applications and speeds up development, by baking in our recommended best practices, providing good default behaviours, catching mistakes, and allowing you to write simpler code.

```bash
# Create a new Next.js application, with typescript
npx create-next-app@latest --ts

# Install redux related tools
npm install @reduxjs/toolkit react-redux
```

### Simple Features of Application

Fast forward a little and we have ourselves a basic application that uses Redux. This is a simple Pokémon application, that you can use to create a team for your next Pokémon journey! If you’re unfamiliar with Pokémon, it is a role-playing game based around building a small team of monsters (Pokémon), to battle other wild monsters and players (Trainers) to become the very best! This application uses the RESTful Pokémon API that can be found [here](https://pokeapi.co/). Even if you’re unfamiliar with Pokémon, the features of this application will still be easy to understand, if you’re already familiar with Redux. For now, let’s just take a look at 2 Redux-related files, and the 3 main components of our application that uses the Redux functionalities. In the videos I included, I made use of Google Chrome’s Inspect tool and [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en), a chrome extension that allows us to debug application state changes. 

{% embed https://youtu.be/Bq85n7LxWQA %}

#### 1. `pokemonSlice.ts`

This file declares the initial state value of our store. In our case, the state that we will be keeping track of, is the Pokémon in our team, in the form of an array. Additionally, we also create 3 reducers - `addPokemon`, `removePokemon` and `removeAllPokemon` - that will mutate our state by adding or removing single/all Pokémon from the state.

```tsx
export const pokemonSlice = createSlice({
  name: "pokemon",
  initialState,
  reducers: {
    addPokemon: (state, action: PayloadAction<PokemonBasicInfo>) => {
      state.value = [...state.value, action.payload];
    },
    removePokemon: (state, action: PayloadAction<PokemonBasicInfo>) => {
      state.value = state.value.filter(
        (poke) => poke.name !== action.payload.name
      );
    },
    removeAllPokemon: (state) => {
      state.value = [];
    },
  },
});

export const selectValue = (state: RootState) => state.pokemonTeam.value;

export const { addPokemon, removePokemon, removeAllPokemon } =
  pokemonSlice.actions;

export default pokemonSlice.reducer;
```

#### 2. `store.ts`

This is the file which holds the whole state tree of our application. `configureStore`, similar to `createStore` , initializes our store with the reducers and initial state that were declared in `pokemonSlice.ts`.

```tsx
import { configureStore } from "@reduxjs/toolkit";
import pokemonTeamReducer from "./slices/pokemonSlice";

export const store = configureStore({
  reducer: {
    pokemonTeam: pokemonTeamReducer,
  },
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
```

#### 3.`Pokedex.tsx`

This component displays a grid of Pokémon, where users can add Pokémon to their team. Users also have the ability to remove a Pokémon from their team if needed. These are both accomplished using Redux’s `useSelector` and `useDispatch`, combined with the reducers that we declared previously

```tsx
const Pokedex = () => {
  const [pokedex, loading] = useGetPokemon();
  const myTeam = useSelector(selectValue);
  const myTeamNames = myTeam.map((poke) => poke.name);
  const dispatch = useDispatch();

  function handleClick(poke: PokemonBasicInfo) {
    if (myTeamNames.length == 6 && !myTeamNames.includes(poke.name)) {
      alert(
        "You can only have 6 Pokémon in your team! Remove one Pokémon to add another."
      );
      return;
    }
    if (!myTeamNames.includes(poke.name)) dispatch(addPokemon(poke));
    else dispatch(removePokemon(poke));
  }

  return (
    <div className={styles.pokedexWrapper}>
      <h1 className={styles.pokedexTitle}>Kanto Pokédex</h1>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <>
          <div className={styles.pokedex}>
            {pokedex.map((poke: PokemonBasicInfo) => (
              <div
                key={poke.name}
                className={
                  myTeamNames.includes(poke.name)
                    ? styles.pokemonChosen
                    : styles.pokemon
                }
                onClick={() => handleClick(poke)}
              >
                <img src={poke.image} alt={poke.name} />
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
};

export default Pokedex;
```

#### 4. `Team.tsx`

This component showcases the Pokémon that we have already added into our team. It does this by reading the state from the store using Redux’s `useSelector`.

```tsx
const Team = () => {
  const myTeam = useSelector(selectValue);
  const dispatch = useDispatch();

  return (
    <div className={styles.teamContainer}>
      <h1 className={styles.header}>My Team</h1>
      {myTeam.length ? (
        <div className={styles.teamWrapper}>
          <div className={styles.teamListing}>
            {myTeam.map((poke: PokemonBasicInfo) => (
              <div key={poke.name} className={styles.pokemon}>
                <img src={poke.image} alt={poke.name} />
                <h1 className={styles.pokemonName}>{poke.name}</h1>
              </div>
            ))}
          </div>
          <button
            className={styles.clearButton}
            onClick={() => {
              dispatch(removeAllPokemon());
            }}
          >
            Reset
          </button>
        </div>
      ) : (
        <h1 className={styles.emptyMessage}>
          Click on a Pokémon to add it to your team!
        </h1>
      )}
    </div>
  );
};

export default Team;
```

#### 5. `_app.tsx`

The entry point for our app is then wrapped with a `Provider` which provisions the `store` we previously created. This allows all its children components to access the store and make modifications to the state.

```tsx
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}
```

For an in-depth look at the project, please visit my [GitHub](https://github.com/Content-Creation-Amir/pokemon-redux-persist-tutorial/tree/no-redux-persist).

### Limitations of Redux Alone

But here’s a problem.

{% embed https://youtu.be/kt241vkQUnI %}

Did you catch that? If you said that the problem was choosing *Oddish* to be part of my team, technically you’re not wrong….. BUT a more drastic issue would be that my team selection gets wiped whenever I refresh the page. What if I forgot what my team selection was before? How would I make sure that the application remembers (persists) the choices (state) that I have made?

## Redux-Persist

Redux-Persist is an add-on library to Redux that will allow us to save our Redux store in the local or session storage. Before I dive deeper into how and why it works, let’s take a look at adding it into our application. This is a simple process as it only requires us to modify our `store.ts` and `_app.tsx` . But before that, let’s install the package.

```bash
# Install redux-persist
npm i redux-persist
```

### Changes /Additions Made

Now, let’s delve deeper into the changes and additions that were made to our existing files.

#### 1. `store.ts`

| Function / Object | Description |
| --- | --- |
| combineReducers() | Merges all the reducers into a single reducer object. Think of it as throwing all our reducers into a giant box, so that we can easily reference all our reducers by pointing to that giant box. |
| persistConfig | This is a configuration object that is used to customize the persistence of our reducers in our store. The key configuration to include is the key and storage. For our case, we our key is simply root and the storage we are using is the session storage which is imported from redux-persist/lib/storage/session . Alternatively, to use local storage, we can instead import storage from redux-persist/lib/storage but of course, the choice is yours. This will determine where our store and state is being stored. You can look at it as the core of redux-persist as it allows the application to remember and persist the data using the local/session storage as its memory. |
| persistReducer() | Takes in the persistConfig object we created earlier, together with the all-in-one reducer to create our persistedReducer. What we are doing is simply applying the persistence configuration on all our reducers |
| configureStore() | If you’re familiar with Redux’s createStore, this is a simply an abstraction/wrapper around that function which helps to add good default to our store setup. This function takes in a configuration object which in our case just includes the persistedReducer we created. This  store is exported and is the same one provisioned via the Provider wrapper in _app.tsx. |
| persistStore() | Finally, we pass our previously created store object into this function, to finalize the creation of a store that is able to persist state, by saving it in the local storage. This persistor is exported to be used in _app.tsx |

```tsx
const persistConfig = {
  key: "root",
  storage,
};

const reducers = combineReducers({ pokemonTeam: pokemonTeamReducer });

const persistedReducer = persistReducer(persistConfig, reducers);

export const store = configureStore({
  reducer: persistedReducer,
});

export let persistor = persistStore(store);

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
```

#### 2. `_app.tsx`

Apart from the store that we already provisioned, to ensure that the persistence configuration is set for all the components in our application, we add another wrapper `PersistGate`. This wrapper takes in the `persistor` that we created in `store.ts` and can also take in a loading component. e.g `loading={<Loading />}`. `PersistGate` will help to delay the rendering of the app until the persisted state has been retrieved from the local/session storage and saved to redux.

```tsx
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <Component {...pageProps} />
      </PersistGate>
    </Provider>
  );
}
```

### Final Product

Add here’s the new and improved Pokémon team builder, now enhanced with redux-persist! As you can see, our application remembers our team, despite refreshing the page! When we look at the Redux DevTools deeper, we can see that Redux performs a persist and rehydrate, allowing our data to be ‘remembered’ on refresh. Additionally, we can also see that our information is being stored in the session storage, in the key `persist:root`.

{% embed https://youtu.be/M9a7V7IFebU %}

Once again, for an in-depth look at the project, please visit my [GitHub](https://github.com/Content-Creation-Amir/pokemon-redux-persist-tutorial/tree/main). 

## Conclusion

Redux-persist is a very helpful addon to Redux that allows our application to persist and remember our state, by saving the data in the browser’s persistent storage. This ensures that the data will still be available for our components to modify and utilise, despite a browser refresh. We are able to further specify which storage to use - local or session -  or customize the persistence of our store by including/excluding desired reducers. With that said, what’s stopping you from simply creating a helper function that adds the state to the storage whenever we do a `useDispatch()`, and check that storage for existing any data whenever we perform a `useSelector()` to retrieve the state? Instead, maybe we could have used `localStorage.setItem("team", [])` and `localStorage.get('team')` ? Let me know your thoughts on which you prefer or if you have any other alternatives for persisting Redux state!

Lastly, if you’re looking for a more in depth understanding of redux-persist and its other features and configurability, here are other helpful articles/video you can visit!

- [*Redux: Persist Your State*](https://medium.com/async/redux-persist-your-state-7ad346c4dd07) by Zack (2017)
- [*The Definitive Guide to Redux Persist*](https://blog.reactnativecoach.com/the-definitive-guide-to-redux-persist-84738167975) by Mark Newton (2017)
- [*Redux-persist: The Good Parts*](https://codeburst.io/redux-persist-the-good-parts-adfab9f91c3b) by Feargal Walsh (2018)
- [*How to persist and rehydrate redux store efficiently*](https://www.youtube.com/watch?v=pEzvtMb0HSI&ab_channel=TheMinimalistCoder) by The Minimalist Coder (2021)