DEV Community

Cover image for How to get started with Redux Toolkit
Natalie Smith 🏳️‍🌈
Natalie Smith 🏳️‍🌈

Posted on

How to get started with Redux Toolkit

What is Redux Toolkit?

To sum it up. It's an easier way to setup and get going with Redux.

We've all been there when it comes to trying to configure a Redux store and in turn, we're left bewildered by the thought it. Or perhaps, it takes too much boilerplate code to get up and running. These are just some of the concerns that Redux Toolkit aims to solve.

The Redux Toolkit package is intended to be the standard way to write Redux logic.

We can't solve every use case, but in the spirit of create-react-app and apollo-boost, we can try to provide some tools that abstract over the setup process and handle the most common use cases, as well as include some useful utilities that will let the user simplify their application code.

Because of that, this package is deliberately limited in scope. It does not address concepts like "reusable encapsulated Redux modules", data caching, folder or file structures, managing entity relationships in the store, and so on. - Redux Toolkit docs

Prerequisite

  • Basics of Redux and React

Installation

Run the following command to install it

# NPM
npm install --save redux react-redux @reduxjs/toolkit

# Yarn
yarn add --save redux react-redux @reduxjs/toolkit

Redux.org recommends that you structure it in the following ways:

  • Feature folder
    • All files for a feature in a single folder
  • Ducks pattern
    • All Redux logic for a feature in a single file

How to setup the Redux Store

I'm going to create a folder called store and create a file called index.js

src > store > index.js

import { configureStore } from '@reduxjs/toolkit'
import { combineReducers } from 'redux'

const reducer = combineReducers({
 // add reducers
})

const store = configureStore({
  reducer,
})

export default store;

You can also do it this way:

src > index.js

import React from 'react'
import { render } from 'react-dom'

import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'

import App from './App'

import rootReducer from './whereeverthislocated'

import './index.css'

const store = configureStore({ reducer: rootReducer })

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

What are slices?

Redux Toolkit has this thing called slices and essentially, it automatically generates reducers, action types, and action creators. Each slice file lives inside a folder inside of src called slices

The initial state

src > slices > count.js

import { createSlice } from '@reduxjs/toolkit'

export const initialState = {
  count: 0
}

Creating a slice

src > slices > count.js

const countSlice = createSlice({
  name: "count",
  initialState,
  reducers: {
    increment: (state) => {
      state.count = state.count + 1;
    },
    decrement: (state) => {
      state.count = state.count - 1;
    }
  }
});
export const { increment, decrement } = countSlice.actions;
export default countSlice.reducer

Getting Redux state in a React component

Before, we used mapStateToProps with the connect() function to get the state from the store and in Redux Toolkit, that is still a viable option. However, with the addition of Hooks, we can use useDispatch and useSelector

Here's a little bit about each hook:

  1. useDispatch
    • Used to "dispatch" actions
  2. useSelector
    • Allows you to "select" data from the Redux store state, using a selector function.
App.js

import React from "react";
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";

import { increment, decrement } from "../slices/count";

export default function App() {
  const dispatch = useDispatch();
  const { count } = useSelector((state) => state).countSlice;

  const handleUp = () => {
    dispatch(increment());
  };
  const handleDown = () => {
    dispatch(decrement());
  };
  return (
    <div className="App">
      <h1>My Amazing Counter</h1>

      <h2>Current Count: {count}</h2>
      <button onClick={handleUp}>UP</button>
      <button onClick={handleDown}>DOWN</button>
    </div>
  );
}

For useSelector, you can also predefine what you want in the count.js file. For example:

src > slices > count.js 

// A selector
export const countSelector = (state) => state.count

And then use that in the App.js file,

App.js

const App = () => {
    const { count } = useSelector(countSelector)
    ...
}

Fetching data from an API

This counter example is pretty basic and you're more likely going to be working with an API of some sorts so let's learn how to fetch and save it to the store.

For this, I'm going to be using the Github API. In the slices folder, create a file called repo.js and add the following:

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const initialState = {
  loading: false,
  hasErrors: false,
  repos: []
};

const reposSlice = createSlice({
  name: "repos",
  initialState,
  reducers: {
    getRepos: (state) => {
      state.loading = true;
    },
    getReposSucess: (state, { payload }) => {
      state.repos = payload;
      state.loading = false;
      state.hasErrors = false;
    },
    getReposFailure: (state) => {
      state.loading = false;
      state.hasErrors = true;
    }
  }
});

export const { getRepos, getReposSucess, getReposFailure } = reposSlice.actions;

// The reducer
export default reposSlice.reducer;

export const fetchRepos = createAsyncThunk(
  "repos/fetchRepos",
  async (thunkAPI) => {
    // Set the loading state to true
    thunkAPI.dispatch(getRepos());

    try {
      const response = await fetch(
        "https://api.github.com/search/repositories?q=react&page=1&per_page=10",
        {
          method: "GET",
          headers: {
            Accept: "application/vnd.github.v3+json"
          }
        }
      );
      const data = await response.json();
      // Set the data 
      thunkAPI.dispatch(getReposSucess(data));
    } catch (error) {
      // Set any erros while trying to fetch
      thunkAPI.dispatch(getReposFailure());
    }
  }
);

A little bit about createAsyncThunk:

  • A thunk is a function that gets returned by another function
  • createAsyncThunk will run a callback when dispatched as well as lifecycle actions based on the returned promise

To learn more about createAsyncThunk you can go here

Displaying the repos

In App.js, add the following:

App.js

import React, { useEffect } from "react";
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import { fetchRepos } from "../slices/repo";

export default function App() {
  const dispatch = useDispatch();
  const { loading, hasErrors, repos } = useSelector(
    (state) => state
  ).reposSlice;

  useEffect(() => {
    dispatch(fetchRepos());
  }, [dispatch]);

  if (loading) {
    return <p>Loading...</p>;
  } else if (hasErrors) {
    return <p>Oh no error! Display something here.</p>;
  }
  console.log(repos.items);
  return <div className="App">loaded</div>;

Similar to our counter example, we're pretty much doing the same steps except we added in the useEffect hook to fetch our data.

If you want to pass parameters to your API call

App.js
. . .

useEffet(() => {
    dispatch(fetchRepos("react"))
}, [dispatch])

. . .
src > slices > repo.js 

export const fetchRepos = createAsyncThunk(
  "repos/fetchRepos",
  async (paramNameHere, thunkAPI) => {
        console.log(paramNameHere)
    . . .
  }
);

And that's it!

Conclusion

In this post, we learned what Redux Toolkit is, how to setup our store, update our store and even learned how to fetch data and save it to the store as well. Redux is complicated and we're probably going to bump into it again so hopefully Redux Toolkit can help you as it has helped me.

Thanks for reading and if I missed something in this post please comment down below, I'm not an expert so feedback is always appreciated.

Cover Image from: https://miro.medium.com/max/800/1*4sxOPaVNwxrfZ9uxVbUaKg.jpeg

Latest comments (2)

Collapse
 
mdsiddiq1234 profile image
Md-Siddiq1234

This implementation is well explained. I have a doubt though. What if I want to perform some other functionalities like creating a repo, updating & deleting it. Do I need to create different slices for that. If not, do I need to have other functions inside the reducer object. Wating for your reply. Thanks

Collapse
 
wasiqahmdzai profile image
Mohammad Wasiq

No, you don't need to create different slices. you can do it by creating reducers for each functionality just as you would do it for fetching repo.