This tutorial explain how you can build an application using modern react redux with redux toolkit. Modern React Redux Tutorials with Redux toolkit - 2020.
Things are changing in front end application development. What we have been using to develop a frontend application is constantly changing to build things faster and efficient. One of them is redux, there are lots of state management options out there to react. but, we have to admit that redux is popular among them.
So, what changed in redux?.. well we are about to find out in this article. Buckle up to learn in this react with redux tutorial.
Note: I assume that you are familiar with redux concepts. If you are new to Redux, I recommend you to checkout this course.
Before we get into the article, let me show you what we are going to build in this article.
This is a simple Forum where you can post a question and comment on it.
Well, the core concepts of redux remain the same. But, the way we implement things is changed in the react-redux environment. With a package called redux-toolkit, we can implement redux in our react application in an easy way.
Setting up redux boilerplate is cumbersome in previous days. redux-toolkit solves that problem for us. Let's see how to use redux-toolkit to build an application.
there are few important changes in the redux toolkit, let's see them
configureStore
we all know that redux store(createStore) handles the actions and reducer in redux. well, configure store is an wrapper around createStore with some default configuration options. it simplifies some configuration pains.
createAction
createAction
returns an action creator function. one important change to note here is that we return an action creator function not an object. in old traditional way, it returns an object with type and payload.here, we return a function .
1// Original approach: write the action type and action creator by hand2const INCREMENT = "INCREMENT"34function incrementOriginal() {5 return { type: INCREMENT }6}78console.log(incrementOriginal())9// {type: "INCREMENT"}1011// Or, use `createAction` to generate the action creator:12const incrementNew = createAction("INCREMENT")1314console.log(incrementNew())15// {type: "INCREMENT"}
createReducer
Let me show two versions of code, see and think which one is more easier to manage.
1//redux action2const increment = payload => {3 return {4 type: "INCREMENT",5 payload,6 }7}89//redux reducer function10export default reducer = (state, action) => {11 switch (action.type) {12 case "INCREMENT":13 return {14 ...state,15 payload,16 }17 default:18 return state19 }20}
Let's look at an another version of code
1const increment = createAction("INCREMENT")2const decrement = createAction("DECREMENT")34const counter = createReducer(0, {5 [increment]: state => state + 1,6 [decrement]: state => state - 1,7})
you can see the difference. right?. well that's makes a lot difference when your application grows. it will easy to maintain in the later version.
createReducer takes initialState and action creator function and use a concepts lookup table
which takes the object key and compares it with action creator and match with the reducer. In that way, you don't need to manually write a if..else or switch case state to manage it.
createSlice
If you think, it's cutting down from writing lots of code. well, there is more. createSlice
provides an option to generate action creator and action types for. you only need to specify the reducer function, initial state, and name for the slice and createSlice take care of everything for you.
1const counterSlice = createSlice({2 name: "counter",3 initialState: 0,4 reducers: {5 increment: state => state + 1,6 decrement: state => state - 1,7 },8})910const store = configureStore({11 reducer: counterSlice.reducer,12})1314document.getElementById("increment").addEventListener("click", () => {15 store.dispatch(counterSlice.actions.increment())16})
Let's start with create-react-app
with redux toolkit template included on it.
1npx create-react-app my-app --template redux
Above command creates a boilerplate for us with recommended folder structure(feature folder) and a simple example code.
Every domain is considered as a feature here. it's better to wrap the functionalities of a particular domain in one feature folder. checkout this article to learn more about it.
Learning bit: https://github.com/erikras/ducks-modular-redux
In our application, there are three domains that we need to cover. they are,
So, create folder structure based on the above domains.
Now, it is time to create redux part of our features.
Let's take the Questions part. it will contains functionalities such as
we need to use createSlice
to create reducer and action creator function. before that, import create slice from toolkit.
1import { createSlice } from "@reduxjs/toolkit";23create a slice function with name, initial State and reducer function.45 export const questionSlice = createSlice({6 name: "questions",7 initialState: [],8 reducers: {9 addQuestion: (state, action) => {10 //Add Question reducer function11 },12 editQuestion: (state, action) => {13 //Edit Question Reducer function14 },15 removeQuestion: (state, action) => {16 //Remove Question Reducer function17 },18 },19 });
Once we create that, we can be able to get all the actions from the slice itself. Here, we have reducer functions addQuestion,editQuestion and removeQuestion. so, createSlice will generate three action creator function with exact name on it.
1export const {2 addQuestion,3 editQuestion,4 removeQuestion,5} = questionSlice.actions
After that, you can write the selector and export the reducer from here.
1export const selectQuestions = state => state.questions23export default questionSlice.reducer
Once you're done with your slice function. map the reducer with redux store.
1import { configureStore } from "@reduxjs/toolkit"2import questionReducer from "../features/Questions/questionSlice"3export default configureStore({4 reducer: {5 questions: questionReducer,6 },7})
Now, we can use the action and selector in our component. create a component Questions.js with basic setup
1iimport React, { useState } from "react";2 import { useDispatch, useSelector } from "react-redux";3 import styled from "styled-components";45 import {6 selectQuestions,7 addQuestion,8 removeQuestion,9 editQuestion,10 } from "./questionSlice";111213 const Questions = () => {1415 const [showAddQuestion, setShowAddQuestion] = useState(false);1617 const questions = useSelector(selectQuestions);18 const dispatch = useDispatch();192021 const onSubmit = () => {2223 };2425 return (26 <Container>27 <QuestionsContainer>28 //Questions Array comes Here29 {questions && questions.length > 0 ? (30 questions.map((question, index) => {31 return (32 <div>{question}</div>33 );34 })35 ) : (36 <div>No Data Found</div>37 )}38 </QuestionsContainer>39 <AddQuestionButtonContainer onClick={() => setShowAddQuestion(true)}>40 <AddQuestionIcon src={plus_icon} />41 <AddQuestionName>Add Question</AddQuestionName>42 </AddQuestionButtonContainer>434445 //When User clicks the add Question, we need to show the Form46 {showAddQuestion ? (47 <FormContainer>48 <FormContainerDiv>49 <FormLabel>Title</FormLabel>50 <FormContainerTitleInput51 type="text"52 value={title}53 onChange={(e) => setTitle(e.target.value)}54 />55 </FormContainerDiv>5657 <FormContainerDiv>58 <FormLabel>Body</FormLabel>59 <FormContainerBodyInput60 type="textarea"61 value={body}62 onChange={(e) => setBody(e.target.value)}63 />64 </FormContainerDiv>65 <AddQuestionButton onClick={onSubmit}>Submit</AddQuestionButton>66 </FormContainer>67 ) : (68 ""69 )}70 </Container>71 );72 };7374 export default Questions;
On the above code, we are using redux hooks useDispatch and useSelector for redux actions and selector.
Firstly, we import the actions and selectors from the slice file.
1import {2 selectQuestions,3 addQuestion,4 removeQuestion,5 editQuestion,6} from "./questionSlice"
After that, we use the selectQuestions in useSelector to get all the data from store.
1const questions = useSelector(selectQuestions)
Then, we render the data in our component
1{2 questions && questions.length > 0 ? (3 questions.map((question, index) => {4 return (5 <QuestionListItem key={index}>6 <Link to={`/question/${question.id}`}>7 <QuestionTitle>{question.title}</QuestionTitle>8 </Link>9 </QuestionListItem>10 )11 })12 ) : (13 <div>No Data Found</div>14 )15}
finally, we have a form which user uses submits the question.
1<FormContainer>2 <FormContainerDiv>3 <FormLabel>Title</FormLabel>4 <FormContainerTitleInput5 type="text"6 value={title}7 onChange={e => setTitle(e.target.value)}8 />9 </FormContainerDiv>1011 <FormContainerDiv>12 <FormLabel>Body</FormLabel>13 <FormContainerBodyInput14 type="textarea"15 value={body}16 onChange={e => setBody(e.target.value)}17 />18 </FormContainerDiv>19 <AddQuestionButton onClick={onSubmit}>Submit</AddQuestionButton>20</FormContainer>
when user clicks the onSubmit, we need to dispatch action prior to it.
1const onSubmit = () => {2 let data = {3 id: questions.length + 1,4 title: title,5 body,6 }78 dispatch(addQuestion(data))9}
Well, that's pretty much of a getting the data and dispatching an action in redux lifecycle.
1import React, { useState } from "react"2import styled from "styled-components"3import { useSelector } from "react-redux"4import { useParams, useHistory } from "react-router-dom"56import Comments from "../Comments/Comments"78const Container = styled.div`9101112`1314const QuestionsContainer = styled.div`151617181920212223`2425const Heading = styled.h2``2627const QuestionLabel = styled.h4`2829`3031const QuestionDetail = props => {32 const { id } = useParams()33 if (!id) {34 }35 const questionDetail = useSelector(state => {36 let question = state.questions.find(question => question.id == id)3738 return question39 })4041 return (42 <Container>43 <QuestionsContainer>44 <Heading>Title:</Heading>45 <QuestionLabel>{questionDetail && questionDetail.title}</QuestionLabel>46 <Heading>Body:</Heading>47 <QuestionLabel>{questionDetail && questionDetail.body}</QuestionLabel>48 </QuestionsContainer>49 {questionDetail ? <Comments id={questionDetail.id} /> : null}50 </Container>51 )52}5354export default QuestionDetail
it contains the details of the questions and comments component. from here, we pass the question id as a props to Comments component
Also, we use useSelector to fetch the questions data from the redux store.
Now, it's time to create slice for comments feature. Here, we need functionalities such as
1import { createSlice } from "@reduxjs/toolkit"23export const commentSlice = createSlice({4 name: "comments",5 initialState: [],6 reducers: {7 addComment: (state, action) => {8 //Add Comment Reducer9 },10 editComment: (state, action) => {11 //Edit Comment Reducer12 },13 removeComment: (state, action) => {14 //Remove Comment Reducer15 },16 },17})
After that, we export the action creator function, selectors and reducer functions.
1export const { addComment, editComment, removeComment } = commentSlice.actions23export const comments = state => state.comments45export default commentSlice.reducer
Finally, update the store with comments reducer function
1import { configureStore } from "@reduxjs/toolkit"2import questionReducer from "../features/Questions/questionSlice"3import commentReducer from "../features/Comments/commentsSlice"4export default configureStore({5 reducer: {6 questions: questionReducer,7 comments: commentReducer,8 },9})
Comments.js
1import React, { useState } from "react"2import styled from "styled-components"3import { useSelector, useDispatch } from "react-redux"45import { addComment } from "./commentsSlice"67const CommentsContainer = styled.div``89const CommentHeading = styled.h4``1011const CommentLists = styled.ul`1213141516171819`2021const AddCommentsInput = styled.input`22232425`2627const CommentListItem = styled.div`2829`3031const CommentTitle = styled.div``3233const Comments = ({ id }) => {34 const [comment, setComment] = useState("")3536 const comments = useSelector(state => {37 let comments = state.comments.filter(comment => comment.questionId == id)3839 return comments40 })41 const dispatch = useDispatch()4243 const onAddComment = e => {44 if (e.key !== "Enter") {45 return46 }4748 if (e.key === "Enter") {49 let data = {50 id: comments && comments.length > 0 ? comments.length + 1 : 1,51 comment: comment,52 questionId: id,53 }5455 dispatch(addComment(data))56 setComment("")57 }58 }59 return (60 <div>61 <CommentsContainer>62 <CommentHeading>Comments</CommentHeading>63 <CommentLists>64 {comments &&65 comments.map(comment => (66 <CommentListItem key={comment.id}>67 <CommentTitle>{comment.comment}</CommentTitle>68 </CommentListItem>69 ))}70 </CommentLists>71 <AddCommentsInput72 type="text"73 value={comment}74 onChange={e => setComment(e.target.value)}75 onKeyPress={onAddComment}76 />77 </CommentsContainer>78 </div>79 )80}8182export default Comments
Complete source code can be found here
Let me give you a quick summary of what we have seen so far. there are four main concepts in redux toolkit. configureStore
, createAction
, createReducer
and createSlice
. each of them makes our redux development easier. Mainly, createSlice
helps us to generates the action creator function and action types just by taking the reducers of the domain. All other concepts remains the same on the application development.
That's it for this article. Learn it and Practice to get better at react redux application development. Happy Coding :-)
No spam, ever. Unsubscribe anytime.