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:
-
useDispatch
- Used to "dispatch" actions
-
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
Top comments (2)
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
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.