Table of Contents
Setting up even a simple Redux store used to be hard. Not anymore. With the help of Redux Toolkit we can create Redux stores much faster and with less code. This tutorial will help you learn how to use Redux Toolkit to create such a simple store and how to implement it in your React app.
Demo on Codesandbox.
About the project and dependencies
In this tutorial we will take a look at how to create a simple redux store with Redux Toolkit. The app we will be working on will be an app for creating books collection. It will allow users to add books to collections and remove them. It will also show all books currently in collection.
The main app state for book collection will be the Redux state. Adding and removing books will be handled by reducer functions. We will create this app with the create-react-app. This will give us what we need to get the React app up and running. For Redux, we will need two additional dependencies.
The first dependency will be react-redux, version 7.2.6. This library contains official React bindings for Redux. Second dependency we will need is @reduxjs/toolkit, version v1.6.2. This library contains tools that make working with Redux much easier. This also includes setting up a redux store.
This tutorial will also use uuid library, version 8.3.2. This library is not necessary for the Redux state. We will use it to generate unique ids for added books. These ids will make it easier to remove specific book from collection. This is all we need to get started.
Creating Redux store
Let’s start with the most interesting part, creating the Redux store. To keep the project tidy, all store-related stuff will be in /store
directory, right inside /src
. The /store
directory will contain two files: books.js
and index.js
. The books.js
will contain Redux state slice for book collection.
The index.js
will contain a very simple setup for the Redux store for our React app.
Redux state slice
The first part of the redux state is will be the redux state slice. We will create the redux slice with createSlice()
, a method provided by @reduxjs/toolkit
. This method allows us to do a couple of things. First, it allows us to define the initial state for our store, the book collection.
It also allows us to define reducer functions for the same store. We will use these reducers later to add books to collection and also to remove them. The initial state for the store will be an object with single property, books
. The value of this property will be an empty array.
For this tutorial we will need two reducers. One will be for adding new book to the Redux store. The second will be removing book from the store. We can call these reducers addBook
and removeBook
. We will define these reducers in an object we will use as a value for reducers
property of createSlice()
method.
The last thing for the createSlice()
method is adding name
property with some name for the store. This is used by Redux toolkit to generate required actions for our Redux store. When we have this setup, the createSlice()
method will automatically generate two things: actions and reducer for our new slice.
It used to be that we had to create these two by ourselves. This is no longer necessary thanks to the Redux toolkit and the whole process is much easier, as you can see. Now, we just have to export those actions and the reducer and we are done here.
// src/store/books.js
// Import createSlice() from Redux toolkit:
import { createSlice } from '@reduxjs/toolkit'
// Initial state for Redux store:
const initialState = {
books: [],
}
// Create Redux state slice
const booksSlice = createSlice({
name: 'books',
initialState, // Define initial state
reducers: {
// Define reducers
addBook: (state, action) => {
// Reducer for adding new book to collection
// Create new state by taking existing books
// and combining them with newly added book:
state.books = [...state.books, action.payload]
},
removeBook: (state, action) => {
// Reducer for removing book from collection
// Filter out a book that matches provided "id":
state.books = state.books.filter((b) => b.id !== action.payload.id)
},
},
})
// Export actions generated by "createSlice()":
export const { addBook, removeBook } = booksSlice.actions
// Export reducer generated by "createSlice()":
export default booksSlice.reducer
Configuring Redux store
We have the setup for Redux state slice, but there is a bit more we have to do. We have to create the Redux store itself and connect it to the reducer generated for our books slice. We will do this by using another method provided by Redux toolkit called configureStore()
.
This method accepts one parameter, configuration object for the Redux store. Right now, we need to define only one property of this object, reducer
. The value of this property will be an object with the reducer we want to use. This will be the booksSlice.reducer
we recently worked with in books.js
.
We have to import this reducer and add it to the reducer
object in store config. After that, we are done as well.
// src/store/index.js
// Import configureStore() from Redux toolkit:
import { configureStore } from '@reduxjs/toolkit'
// Import books reducer:
import booksReducer from './books'
// Create Redux store:
export const store = configureStore({
reducer: {
booksReducer, // Add books reducer
},
})
Adding Redux provider
Both, Redux slice and store are ready. There is one last step we have to make. We have to create Redux provider and connect it with the store we just finished. This will be very fast. We have to go to the main file that renders the main React App
component. This will probably be index.js
in src/
.
Here, we have to import Provider
component from react-redux
library and our Redux store, we exported from src/store/index.js
. We will add the Provider
component to the component tree and set our store as a value for Provider’s store
prop. This will make the store available for the whole app.
// src/index.jsx
// Import React deps:
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
// Import Redux provider:
import { Provider } from 'react-redux'
// Import some styles:
import './styles.css'
// Import redux store:
import { store } from './store'
// Import main App component:
import { App } from './App'
// Render the React app:
const rootElement = document.getElementById('root')
ReactDOM.render(
<StrictMode>
{/* Add Redux provider */}
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
)
The Form component
The first component for our books collection app will be form for adding books. This component will have its own local state (useState), for storing information about book’s title and author. We will store these data in Redux store using dispatch()
method returned from useDispatch()
hook that is provided by react-redux
.
To add new book, we use the addBook
reducer we exported from src/store/books.js
. We will pass this reducer to the dispatch()
method. Payload for the addBook
reducer will be data for the book: title, author and unique id generated by uuid
. After new book is added, we will reset local state for title and author.
// src/Form.jsx
// Import dependencies:
import { memo, useCallback, useState } from 'react'
import { useDispatch } from 'react-redux'
import { v4 as uuidV4 } from 'uuid'
// Import addBook reducer:
import { addBook } from './store/books'
// Create Form component:
export const Form = memo(() => {
// Prepare Redux dispatch method:
const dispatch = useDispatch()
// Create form states:
const [bookTitle, setBookTitle] = useState('')
const [bookAuthor, setBookAuthor] = useState('')
// Add form onSubmit handler:
const onFormSubmit = useCallback(
(event) => {
event.preventDefault()
// Dispatch addBook reducer with payload
// containing data for new book:
dispatch(
addBook({
bookTitle,
bookAuthor,
id: uuidV4(),
})
)
// Reset form states:
setBookTitle('')
setBookAuthor('')
},
[bookAuthor, bookTitle, dispatch]
)
return (
<form onSubmit={onFormSubmit}>
<fieldset>
<label htmlFor="title">Book title</label>
<input
type="text"
id="title"
value={bookTitle}
onChange={(event) => setBookTitle(event.target.value)}
/>
</fieldset>
<fieldset>
<label htmlFor="author">Book author</label>
<input
type="text"
id="author"
value={bookAuthor}
onChange={(event) => setBookAuthor(event.target.value)}
/>
</fieldset>
<fieldset>
<button type="submit">Add book</button>
</fieldset>
</form>
)
})
The BookList component
Thanks to the form, we can store books in our Redux store. Next thing we can do is create some list showing all books that are currently in the store. We can also add some button to remove specific book from the list, and the store. To do this, we will have to do a couple of things.
First, we will again have to use the useDispatch()
hook to get the dispatch()
method so we can update our store. Now, the reducer we will dispatch will be the removeBook
. We will import this reducer from src/store/books.js
. We will handle removing books with new handler function called onBookRemove()
.
This function will accept one parameter: bookId
. It will pass this id as a payload to the removeBook
reducer, and pass this reducer to the dispatch()
method. The payload will be an object with id
property and id of the book we want to remove. In order to list the books in store, we will need to get them somehow.
We can get those books by using the useSelector()
hook, also provided by react-redux
. This hook allows us to access the redux store’s state, the books state in our case. All we need is to provide this hook with a selector function to get the data we want.
The hook passes state
as an argument to the selector function. We will use this argument to get the booksReducer
we registered when we configured the Redux store (in src/store/index.js
). This reducer contains the slice we created for our books collection. The name of this slice is books
.
We defined this name in src/store/books.js
. It is the name
property in createSlice()
method. We will now use this name to access the books slice. The state of this slice is the array for our books collection. When we have this, we can use map()
to iterate over the books array to generate and display a list of all books in collection.
// src/BookList.jsx
// Import dependencies:
import { memo, useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
// Import removeBook reducer:
import { removeBook } from './store/books'
// Create BooksList component:
export const BooksList = memo(() => {
// Get books from Redux store:
const books = useSelector((state) => state.booksReducer.books)
// Prepare dispatch method:
const dispatch = useDispatch()
// Create handler for removing book:
const onBookRemove = useCallback(
(bookId) => {
dispatch(removeBook({ id: bookId }))
},
[dispatch]
)
return (
<div className="booklist">
{books.map((book) => (
<div key={book.bookTitle} className="book">
{`${book.bookTitle} by ${book.bookAuthor}`}
<button
className="removeButton"
onClick={() => onBookRemove(book.id)}
>
×
</button>
</div>
))}
</div>
)
})
The App component
Redux, form and book list components are ready. We can now put these components together in the App
component. Along with this, we can also add a heading showing how many books we have in our collection. We will get this number by using the useSelector
hook to get the books state.
In this case, instead of asking for state.booksReducer.books
we can ask directly for the number of books in the state. We can do this by adding the length
property: state.booksReducer.books.length
. This will return the number of books in the store instead of the array itself.
// src/App.jsx
// Import dependencies:
import { memo } from 'react'
import { useSelector } from 'react-redux'
// Import components:
import { BooksList } from './BooksList'
import { Form } from './Form'
// Create App component:
export const App = memo(() => {
// Get books from Redux store:
const booksCount = useSelector((state) => state.booksReducer.books.length)
return (
<div className="app">
<h1>Books in collection: {booksCount}</h1>
<Form />
{booksCount > 0 && <BooksList />}
</div>
)
})
The index file
The index.jsx
where we added the Redux Provider
component stayed the same. No change is necessary here.
// src/index.jsx
// Import React deps:
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
// Import Redux provider:
import { Provider } from 'react-redux'
// Import some styles:
import './styles.css'
// Import redux store:
import { store } from './store'
// Import main App component:
import { App } from './App'
// Render the React app:
const rootElement = document.getElementById('root')
ReactDOM.render(
<StrictMode>
{/* Add Redux provider */}
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
)
Conclusion: How to create simple Redux store with Redux toolkit
The whole flow of setting up even a simple Redux store used to be tedious and lengthy. Developers had to write a lot of code to just get started. This is no longer the case thanks to Redux toolkit. I hope that this tutorial helped you learn how to create a simple Redux store and how to implement it in your React app.
If you liked this article, please subscribe so you don't miss any future post.
If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂