Redux is a very popular state container for javascript and react apps. We can use it in react-native also. This is because we can handle app states globally anywhere throughout the app. Previously we are using redux-thunk or redux-saga for managing the redux state, but I recommend using redux-toolkit because it makes things easier for us. So in this article, I will explain how we can integrate the redux-toolkit in react js.
You will get official documentation for redux-toolkit from here Redux-Toolkit Link and basic redux from here Redux Link.
1. First, you need to create one react app using the create-react-app command as given below.
npx create-react-app reduxtoolkitdemo
2. Now you need to install dependencies like those given below.
yarn add @reduxjs/toolkit react-redux
3. So the redux toolkit includes many things like configureStore(), createReducer(), createAction(), createSlice(), createAsyncThunk(); I will explain them one by one.
4. First, we have to make a folder structure for managing the project. So we need to create one folder named ‘redux.’In that folder, we have to create one more folder called ‘ store,’ which contains a store.js file.
5. In the store.js file, we have to configure our redux app store using the configureStore() method. configureStore() method mostly all features of createStore(). It provides more features than createStore for developing apps in a better way.
It can redux-thunk middleware by default, and we can see it acting as a Redux dev tool in the browser. As of now, you can write the below code in your store.js file for beginning the app. We will modify this screen as per our requirements.
import { configureStore } from '@reduxjs/toolkit' export const store = configureStore({ reducer: { }, })
6. Now, if we want to access all the data from a store anywhere in the app, we have to configure it with the Provider component from ‘react-redux, which wraps our whole app. You can do this as given below.
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { Provider } from 'react-redux' import { store } from './redux/store/store'; import { responsiveFontSizes, ThemeProvider } from '@mui/material'; import theme from './constants/theme'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <ThemeProvider theme={theme}> <App /> </ThemeProvider> </Provider> </React.StrictMode> ); reportWebVitals();
7. Now we will go for createSlice(). It takes a reducer function object, a slice name and an initial state value and automatically generates a slice reducer using the appropriate action builder and action type.
8. Now create one file named counterSlice.js. First, we have to import the createSlice method from the toolkit as given below.
import { createSlice } from '@reduxjs/toolkit'
9. It requires a unique name for identifying the slice for the store, initial value and one or more reducer functions to define how the state can be updated, and we can use it in required screens. After creating the slice, we export the auto-generated redux actions and reducer for the currentSlice. You can write code by the given below.
import { createSlice } from '@reduxjs/toolkit' const initialState = { value: 0, } export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1 }, decrement: (state) => { state.value -= 1 }, incrementByAmount: (state, action) => { state.value += action.payload }, }, }) // Action creators are generated for each case reducer function export const { increment, decrement, incrementByAmount } = counterSlice.actions const counterReducer = counterSlice.reducer export default counterReducer
10. Now we have to add a reducer function of the current slice to the store. So after adding it, we can use the state anywhere in the app. So, for example, you can write code like the one below.
import { configureStore } from '@reduxjs/toolkit' import counterReducer from '../slices/counterSlice' export const store = configureStore({ reducer: { counter: counterReducer, }, })
11. Now we will use redux state and actions in our app screens or components. We are using a functional part, so we have to use redux hooks to get data from the redux state using useSelector and dispatch the redux actions using useDispatch.
So first, we must create one component or screen file called ‘Counter.js’ where we will perform our activities. Then, you can write the code given below.
import React, { } from 'react' import { useSelector, useDispatch } from 'react-redux' import { decrement, increment, } from '../redux/slices/counterSlice' export function Counter() { const count = useSelector((state) => state?.counter?.value) const dispatch = useDispatch() return ( <div style={{ padding: '20px' }}> <div> <button aria-label="Increment value" onClick={() => {dispatch(increment())}} > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} > Decrement </button> </div> </div> ) }
12. So here, as per the above code, when we dispatch increment action, it will automatically create an action and type like the given below. So after that redux reducer state will increment the count as we wrote previously. And when the state updates, our Counter.js component updates the value, and we can show it on screens.
13. Now, if we want to define action and reducer in our way, we can also do it using createAction and createReducer methods of the toolkit. So I will explain it step by step.
14. So we have to import createAction from the toolkit and create and export actions with unique types like those given below. Here I created one action called multiplication with a special type counter/multiply using createAction method.
export const multiplication = createAction("counter/multiply"); export const incrementAmount = createAction("counter/incrementAmount");
15. Now we will create our reducer functions and export them. Previously we used switch cases for every action type and performed operations based on it. Now we will use the createReducer method of the toolkit and create the reducer. We can make cases in reducer using two ways, First is ‘builder callback’ notation, and the other is ‘map object’ notation.
16. So first, we will create a reducer using the ‘builder callback’ notation. So, in this case, it accepts two arguments: the initial value for the state and the callback function with one builder argument. Builder obj will provide a feature to add cases using addCase for defining actions the reducer handles. You can do it as given below.
import { createAction, createReducer } from '@reduxjs/toolkit' const initialStateForExample = { value: 1 } export const multiplication = createAction("counter/multiply"); export const incrementAmount = createAction("counter/incrementAmount"); //reducer //way1 using builder callback notation export const exampleSlice = createReducer(initialStateForExample, (builder) => { builder.addCase(multiplication, (state, action) => { state.value = state.value * 2 }); builder.addCase(incrementAmount, (state, action) => { state.value = state.value + action.payload; }); })
17. builder.addCase() has two arguments we need to pass, first is action type, for which case we need to handle reducer state, and the other is a reducer, which contains state and action. So the form has redux state value, and the act includes the type and coming payload.
18. We have to add this exampleSlice reducer to our store given below. I created it with another name for better understanding.
import { configureStore } from '@reduxjs/toolkit' import counterReducer, { exampleSlice } from '../slices/counterSlice' export const store = configureStore({ reducer: { counter: counterReducer, example: exampleSlice, }, })
19. Now we will use this in our component. We will do the same kind of dispatch, and the reducer will update the value and can see the updated value in the same part. You can do it like the one given below.
import React, { useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { decrement, increment, incrementAmount, multiplication } from '../redux/slices/counterSlice' import { fetchAllPosts } from '../redux/slices/postSlice' export function Counter() { const count = useSelector((state) => state?.counter?.value) const example = useSelector((state) => state?.example) const dispatch = useDispatch() useEffect(() => { dispatch(fetchAllPosts()) }, []) return ( <div style={{ padding: '20px' }}> <div> <button aria-label="Increment value" onClick={() => { dispatch(increment()) }} > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} > Decrement </button> <h4>Multiply example</h4> <span>{example?.value}</span> <button aria-label="Multiply value" onClick={() => { dispatch(multiplication()) }} > Multiply </button> <button aria-label="Amount value" onClick={() => { dispatch(incrementAmount(5)) }} > Add 5 Amount </button> </div> </div> ) }
20. Now we will create cases using map object notation. It accepts two arguments for basic usage. First is the initial state value, and second is the actionMap object, which handles the reducer for each required case. I will use the same action for incrementAmount and multiplication action types. You can do it as given below.
// way2 using map object notation export const exampleSlice = createReducer(initialStateForExample, { [multiplication]: (state, action) => { state.value = state.value * 2 }, [incrementAmount]: (state, action) => { state.value = state.value + action.payload; } })
21. You can use this in the same way as in steps 18 and 19.
22. When we want to implement an asynchronous function and need to manage response based on that, at that time, we have to go for the createAsyncThunk() method approach. It is a callback function that takes a string representing a Redux action type and should return a promise.
It provides a thunk action creator to execute the promise callback and dispatch the lifecycle actions based on the returned contract. It generates promise lifetime action types depending on the action type prefix that you pass in. It does not create any reducer functions.
Using this, we can write our logic and reducer, which are appropriate for our app. createAsyncThunk accepts 3 params: a string action type value, a payloadCreator callback, and an options object. We can do it in a simple way like the one given below.
import { createAsyncThunk, } from "@reduxjs/toolkit"; import { signInWithEmailAndPassword } from "firebase/auth"; import { FirebaseAuth } from "../../utils/firebase"; import { showCustomToast, toastType } from "../../utils/helpers"; export const loginWithPassword = createAsyncThunk('app/login', async (payload, { }) => { const { email, password } = payload const response = await signInWithEmailAndPassword(FirebaseAuth, email, password); console.log("response :", response); return { response } })
23. If we want to use this promise value and response in the component, we must add this thunk action to the reducer. So the createSlice method has one prop called extraReducers to add our case for loginWithPassword type. We can use this case using the builder callback notation method because it’s a better approach, as given below.
import { createAsyncThunk, createSlice, } from "@reduxjs/toolkit"; import { signInWithEmailAndPassword } from "firebase/auth"; import { FirebaseAuth } from "../../utils/firebase"; export const loginWithPassword = createAsyncThunk('app/login', async (payload, { }) => { const { email, password } = payload const response = await signInWithEmailAndPassword(FirebaseAuth, email, password); console.log("response :", response); return { response } }) export const loginSlice = createSlice({ name: "login", initialState: { loading: false, success: false, error: null, }, extraReducers: (builder) => { builder.addCase(loginWithPassword.fulfilled, (state, action) => { console.log("action :", action); state.loading = false; if (action?.payload?.success) { state.success = true; state.error = null; } else { state.success = false; state.error = action?.payload?.error; } }) } })
24. The payloadCreator function can be called with 2 types of arguments. The first is the payload, and the second contains thunkAPI.thunkAPI objects have parameters of Redux thunk functions like dispatch and getState, but it also includes some additional parameters like rejectWithValue, fulfillWithValue, extra, etc.
So as of now, we will use rejectWithValue, fulfillWithValue for our requirements. For more detail, you can check here.
25. For Asynchronous API calls if we want to handle success and failure state we can use rejectWithValue, fulfillWithValue for asynchronous calls like given below:
import { createAsyncThunk, createSlice, } from "@reduxjs/toolkit"; import { signInWithEmailAndPassword } from "firebase/auth"; import { FirebaseAuth } from "../../utils/firebase"; import { showCustomToast, toastType } from "../../utils/helpers"; export const loginWithPassword = createAsyncThunk('app/login', async (payload, { getState, rejectWithValue, fulfillWithValue }) => { try { const { email, password } = payload const response = await signInWithEmailAndPassword(FirebaseAuth, email, password); console.log("response :", response); return fulfillWithValue({ success: true, response }) } catch (error) { console.log("Error :", error); const errorCode = error.code; if (errorCode == 'auth/user-not-found') { showCustomToast("User not found.Please do sign up", toastType.e) } else if (errorCode == 'auth/wrong-password') { showCustomToast("Password is not correct", toastType.e) } else { showCustomToast("Something went wrong", toastType.e) } return rejectWithValue({ success: false, error, }) } })
26. So now this redux thunk actions (loginWithPassword) auto generates 3 action types. First, to manage the pending state before calling an asynchronous function, and second, to handle data when we got successfully from asynchronous operations.
And last for the rejected state if it fails from the asynchronous process. In the Previous step, we implemented the basic flow. Now we will go with more expansion.
export const loginSlice = createSlice({ name: "login", initialState: { loading: false, success: false, error: null, }, extraReducers: (builder) => { builder.addCase(loginWithPassword.pending, (state, action) => { state.loading = true; state.success = false; state.error = null; }) builder.addCase(loginWithPassword.fulfilled, (state, action) => { console.log("action :", action); state.loading = false; if (action?.payload?.success) { state.success = true; state.error = null; } else { state.success = false; state.error = action?.payload?.error; } }) builder.addCase(loginWithPassword.rejected, (state, action) => { console.log("action :", action); state.loading = false; state.success = false; state.error = action?.payload; }) } })
So when an asynchronous action fails, it will go with the loginWithPassword.rejected case, and when it successfully passes, it will go with loginWithPassword.fulfilled case. So now we will use this thunk action with our component to go ahead.
27. In your component, you have to call this async thunk action like the given below. So we can dispatch an async thunk function, and after that, it will manage the state after the response comes from API.
const [email, setEmail] = useState("") const [password, setPassword] = useState(""); const dispatch = useDispatch(); const onClickLogin = () => { dispatch(loginWithPassword({ email, password })) }
28. If we want to handle results based on thunk response, we can also because thunk always returns results with promises of fulfilled or rejected values, as we implemented previously. So we can do the same kind of thing in our component also.
The dispatched thunk’s promise contains an unwrap property that can be used to throw either the error or, if available, the payload provided by rejectWithValue from a rejected action or to retrieve the payload of a fulfilled action. We can do it like the given below:
dispatch(loginWithPassword({ email, password })).unwrap() .then((originalPromiseResult) => { console.log("originalPromiseResult:", originalPromiseResult); // handle result here if (originalPromiseResult.success) { localStorage.setItem(isLogin, "1") navigate('/dashboard', { replace: true }) } }) .catch((rejectedValueOrSerializedError) => { console.log("originalPromiseResult:", rejectedValueOrSerializedError); // handle error here })
Code Repo Link: Click here
So Here I have explained how we can implement the redux toolkit with react js. This Redux Toolkit is a very useful concept to make our app more productive and executed smoothly.
It also contains a redux thunk module, so we can do all things using one module, and also, we do not need to create extra files for reducer, action, and action types. So We can implement it in Production and live apps also.
Ronak is a React-Native developer with more than 4 years of experience in developing mobile applications for Android and iOS. He is dedicated to his work and passion. He worked in different kinds of application industries that have responsive designs. He also has knowledge of how to make an app with expo cli and react-native web and some basic understanding of react.js.
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowThe Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork