The Redux Toolkit documentation calls the library a better way to write Redux logic for React apps and a simple and efficient toolkit for Redux development. In this article, you will learn about the Redux toolkit by building an app that tracks project issues.
If you are a React developer working on a complex application, you will need to use global state management for your app at some point. React Redux is one of the most popular libraries for state management used by many developers. However, React Redux has a complex setup process that I’ve found inefficient, not to mention it requires a lot of boilerplate code. The official developer of Redux developed the Redux Toolkit to simplify the process.
This article is for those with enough knowledge of React and TypeScript to work with Redux.
About Redux
Redux is the global state management library for React applications. If you have used useState()
hooks for managing your app state, you will find it hard to access the
state when you need it in the other parts of the application. With useState()
hooks, the state can be passed from the parent component to the child, and you will be stuck with the problem of prop drilling if you need to pass it to multiple children. That’s where Redux comes in to manage the application state.
Introducing Redux Toolkit
Redux Toolkit is a set of opinionated and standardised tools that simplify application development using the Redux state management library.
“
It eliminates the need to write standard Redux setup code, such as defining actions, reducers, and store configuration, which can be a significant amount of code to write and maintain.
Jerry Navi has a great tutorial that shows the full Redux setup process.
Why I Prefer Redux Toolkit Over Redux
The Redux Toolkit has several key features which make me use this library over plain Redux:
- Defining reducers
With Redux Toolkit, you can specify a slice with a few lines of code to define a reducer instead of defining actions and reducers separately, like Redux. - Immutability helpers
Redux Toolkit includes a set of utility functions that make it easy to update objects and arrays in an immutable way. This makes writing code that follows the Redux principles of immutability simpler. - Built-in middleware
Redux Toolkit includes built-in middleware that can handle asynchronous request tasks. - DevTools integration
Redux Toolkit includes integration with the Redux DevTools browser extension, which makes it easier to debug and analyse Redux code.
Using Redux Toolkit To Build A Project Issue Tracker
I think the best way to explain the value and benefits of using Redux Toolkit is simply to show them to you in a real-world context. So, let’s develop an app with it that is designed to create and track GitHub issues.
You can follow along with the code examples as we go and reference the full code anytime by grabbing it from GitHub. There is also a live deployment of this example that you can check out.
Start creating a new React app with the following command:
This generates a folder for our project with the basic files we need for development. The –template typescript
part of the command is used to add TypeScript to the stack.
Now,
let’s install the dependencies packages required for our project and
build the primary UI for the application before we implement Redux
Toolkit. First, navigate to the project_issue_tracker
project folder we just created:
Then run the following command to install Material UI and Emotion, where the former is a design library we can use to style components, and the latter enables writing CSS in JavaScript files.
Now we can install Redix Toolkit and Redux itself:
We have everything we need to start developing! We can start by building the user interface.
Developing The User Interface
In this section, we will be developing the UI of the app. Open the main project folder and create a new components
subfolder directly in the root. Inside this new folder, create a new file called ProjectCard.tsx
. This is where we will write the code for a ProjectCard
component that contains information about an open issue in the project issue tracker.
Let’s import some design elements from the Material UI package we installed to the new /components/ProjectCard.tsx
file to get us started:
This
creates the project card that displays an issue title, issue priority
level, and the time the issue was “opened.” Notice that we are using an issueTitle
prop that will be passed to the ProjectCard
component to render the issue with a provided title.
Now, let’s create the component for the app’s HomePage
to display all the issues. We’ll add a small form to the page for
submitting new issues that contain a text field for entering the issue
name and a button to submit the form. We can do that by opening up the src/HomePage.tsx
file in the project folder and importing React’s useState
hook, a few more styled elements from Material UI, and the ProjectCard
component we set up earlier:
This results in a new HomePage
component that a user can interact with to add new issues by entering
an issue name in a form text input. When the issue is submitted, a new ProjectCard
component is added to the HomePage
, which acts as an index for viewing all open issues.
The only thing left for the interface is to render the HomePage
, which we can do by adding it to the App.tsx
file.
Using Redux Toolkit
Now
that our UI is finalised, we can move on to implementing Redux Toolkit
to manage the state of this app. We will use Redux Toolkit to manage the
state of the ProjectCard
list by storing all the issues in a store that can be accessed from anywhere in the application.
Before we move to the actual implementation, let’s understand a few Redux Toolkit concepts to help understand what we’re implementing:
createSlice
This function makes it easy to define the reducer, actions, and theinitialState
under one object. Unlike the plain redux, you don’t need to use a switch for actions and need to define the actions separately. This function accepts an object as a name (i.e., the name of the slice) and the initial state of the store and the reducer, where you define all the reducers along with their action types.configureStore
This function is an abstraction for the ReduxcreateStore()
function. It removes the dependency of defining reducers separately and creating a store again. This way, the store is configured automatically and can be passed to theProvider
.createAsyncThunk
This function simplifies making asynchronous calls. It automatically dispatches many different actions for managing the state of the calls and provides a standardised way to handle errors.
Let’s implement all of this! We will create the issueReducer
with an addIssue()
action that adds any new submitted issue to the projectIssues
store. This can be done by creating a new file in src/redux/
called IssueReducer.ts
with this code:
Let’s understand each part of the code. First, we are importing the necessary functions from the Redux @reduxjs/toolkit
package.
Then, we create the type definition of our initial state and initialise the initialState
for the issueReducer
. The initialState
has a projectIssues[]
list that will be used to store all the submitted issues. We can have as many properties defined in the initialState
as we need for the application.
Thirdly, we are defining the issueSlice
using Redux Toolkit’s createSlice
function, which has the logic of the issueReducer
as well as the different actions associated with it. createSlice
accepts an object with a few properties, including:
name
: the name of the slice,initialState
: the initial state of the reducer function,reducers
: an object that accepts different actions we want to define for our reducer.
The slice name for the issueReducer
is issueSlice
. The initalState
of it is defined, and a single adIssue
action is associated with it. The addIssue
action is dispatched whenever a new issue is submitted. We can have
other actions defined, too, if the app requires it, but this is all we
need for this example.
Finally, in the last part of the code, we export the actions associated with our reducer and the issueSlice
reducer. We have fully implemented our issueReducer
, which stores all the submitted issues by dispatching the addIssue
action.
Now let’s configure the issueReducer
in our store so we can use it in the app. Create a new file in src/redux/
called index.ts
, and add the following code:
This code configures and creates the store using the configureStore()
function that accepts a reducer where we can pass all of the different reducers.
We
are done adding the reducer and configuring the store with Redux
Toolkit. Let’s do the final step of passing the store to our app. Start
by updating the App.tsx
file to pass the store using the Provider
:
Here, you can see that we are importing the store and directly passing through the Provider
.
We don’t need to write anything extra to create a store or configure
DevTools like we would using plain Redux. This is definitely one of the
ways Redux Toolkit streamlines things.
OK, we have successfully
set up a store and a reducer for our app with Redux Toolkit. Let’s use
our app now and see if it works. To quickly sum things up, the dispatch()
function is used to dispatch any actions to the store, and useSelector()
is used for accessing any state properties.
We will dispatch the addIssue
action when the form button is clicked:
To access the projectIssue
list stored in our reducer store, we can make use of useSelector()
like this:
Finally, we can render all the issues by map()
-ping the issueList
to the ProjectCard
component:
The final code for HomePage.tsx
looks like this:
Now, when we add and submit an issue using the form, that issue will be rendered on the homepage.
This section covered how to define any reducer and how they’re used in the app. The following section will cover how Redux Toolkit makes asynchronous calls a relatively simple task.
Making Asynchronous Calls With Redux Toolkit
We
implemented our store to save and render any newly added issue to our
app. What if we want to call GitHub API for any repository and list all
the issues of it in our app? In this section, we will see how to use the
createAsyncThunk()
API with the slice to get data and render all the repository issues using an API call.
I always prefer to use the createAsyncThunk()
API of the redux toolkit because it standardises the way different states are handled, such as loading
, error
, and fulfilled
. Another reason is that we don’t need to add extra configurations for the middleware.
Let’s add the code for creating a GithubIssue
reducer first before we break it down to understand what’s happening. Add a new GithubIssueReducer.ts
file in the /redux
folder and add this code:
Let’s understand the fetchIssues
part first:
- We are using the
createAsyncThunk()
API provided by the Redux Toolkit. It helps create asynchronous actions and handles the app’s loading and error states. - The action type name is the first argument passed to
createAsyncThunk()
. The specific action type name we have defined isgithubIssue/fetchIssues
. - The second argument is a function that returns a
Promise
, which resolves to the value that dispatches the action. This is when the asynchronous function fetches data from a GitHub API endpoint and maps the response data to a list of issue titles. - The third
argument is an object that contains configuration options for the async
thunk. In this case, we have specified that the async thunk will not be
dispatched with any arguments (hence the
void
type) and that if thePromise
returned by the async function is rejected, the async thunk will return an action with a rejected status along with arejectValue
property that contains the string “Failed to fetch issues.”
When this action is dispatched, the API calls will be made, and the githubIssuesList
data will be stored. We can follow this exact same sequence of steps to make any API calls we need.
The second section of the code is similar to what we used when we created the issueSlice
, but with three differences:
extraReducers
This object contains the reducers logic for the reducers not defined in thecreateSlice
reducers object. It takes a builder object where different cases can be added usingaddCase
for specific action types.addCase
This method on the builder object creates a new case for the reducer function.- API call states
The callback function passed to theaddCase
method is dispatched bycreateAsyncThunk()
, which updates the different store objects based on the API call states (pending
,fulfilled
, anderror
).
We can now use the GithubIssue
reducer actions and the store in our app. Let’s add the GithubIssueReducer
to our store first. Update the /redux/index.ts
file with this code:
We just added the GithubIssueReducer
to our store with the name mapped to githubIssue
. We can now use this reducer in our HomePage
component to dispatch the fetchIssues()
and populate our page with all the issues received from the GitHub API repo.
This updates the code in HomePage.tsx
with two minor changes:
- We dispatch
fetchIssue
and use thecreateAsync()
action to make the API calls under theuseEffect
hook. - We use the
loading
anderror
states when the component renders.
Now, when loading the app, you will first see the “Loading” text rendered, and once the API call is fulfilled, the issuesList
will be populated with all the titles of GitHub issues fetched from the repo.
Once again, the complete code for this project can be found on GitHub. You can also check out a live deployment of the app, which displays all the issues fetched from GitHub.
Conclusion
There we have it! We used Redux Toolkit in a React TypeScript application to build a fully functional project issue tracker that syncs with GitHub and allows us to create new issues directly from the app.
We learned many of the foundational concepts of Redux Toolkit, such as defining reducers, immutability helpers, built-in middleware, and DevTools integration. I hope you feel powered to use Redux Toolkit effectively in your projects. With Redux Toolkit, you can improve the performance and scalability of your React applications by effectively managing the global state.
No comments:
Post a Comment