Redux and toolkit simple workflow
July 31, 2024
Code
React
Content
- Why use Redux in state management - solve problems of prop drilling
- npm packages related to redux
- Typical workflow of using redux, react-redux and redux toolkit
Why use Redux
Redux is a centralized state management container that can manage the entire application's state all in one place. The emergence of Redux has solved the problem of prop drilling, especially when the application has multiple component hierarchies or when there is extensive state sharing between components.
Solve the problem of Prop drilling
Prop drilling: In React applications, props need to be passed through multiple layers of components to reach the target child component. Some components involved in this process might not need the state. This can lead to issues with code maintainability and scalability.
State transmission between functional components typically requires the use of props, which can result in unnecessary chains of prop passing. Thus, there arises a need for context to manage a state that needs to be passed around or shared. Redux addresses this by providing a centralized global store that holds all states. This allows all components to directly access their required states from the global store, rather than relying on prop passing.
useState + Context to manage the state
In simple scenarios, we can also use React's
useState
for state management, which is for managing state within a single component. Additionally, the Context API
can be used to manage some of the states shared among several components, such as sharing the theme.Context can be placed in the nearest common parent components of the state to share, which ensures that only relevant components have access to state changes.
For instance, if
Component1
and Component2
are sibling components with a common parent component ComponentParent
, we can set up Context in ComponentParent
to facilitate state sharing among them.import React, { useState } from 'react'; import ThemeContext from './ThemeContext'; import Component1 from './Component1'; import Component2 from './Component2'; const ComponentParent = () => { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> <Component1 /> <Component2 /> <button onClick={toggleTheme}>Toggle Theme</button> </ThemeContext.Provider> ); }; export default ComponentParent;
Redux
Compared with Context API, Redux provides a more fine-grained management of the entire application's state, making it a more comprehensive and systematic tool for state management. It is also one way to implement the Flux architecture.
Redux packages
1 redux
npm install redux
2 react-redux
React-Redux is used for integrating Redux in React applications, it provides necessary React components and hooks such as the
<Provider>
component, and hooks like useSelector
and useDispatch
(used for directly accessing the state of the Redux store and dispatching actions).npm install react-redux
3 Redux toolkit
Redux Toolkit is officially recommended by Redux, designed to simplify the building process of Redux applications.
RTK offers APIs such as
createSlice
and createAsyncThunk
, which streamline the usage of Redux, making it easier for developers to define reducers, generate action creators, and handle asynchronous logic.npm install @reduxjs/toolkit
4 For async functions - redux-thunk
npm install redux-thunk
Core Concepts
1 store
Store is the key of Redux. It stores all states and use reducer to operate states.
// ./src/store/index.js import { configureStore } from "@reduxjs/toolkit"; import counterSlice from "./slices/counter/counterSlice"; const store = configureStore({ reducer: { counter: counterSlice.reducer }, }); export default store;
2 Provider
Provider component can make redux store accessible for the entire application, which usually is added in the top component.
import '@/styles/globals.css' import type { AppProps } from 'next/app' import { Provider } from 'react-redux' import store from '@/store/index' export default function App({ Component, pageProps: { ...pageProps }, }: AppProps) { return ( <Provider store={store}> <main className='w-screen h-screen'> <Component {...pageProps} /> </main> </Provider> ) }
3 Get and Change state in components
In components, you can use
useSelector
to get Redux store state and useDispatch
to dispatch actions to change states.Workflow
Only redux, react-redux and @reduxjs/toolkit related.
1 install Redux Toolkit and React-Redux
npm install redux react-redux @reduxjs/toolkit
2 create Redux Store
// ./src/store/index.js import { configureStore } from "@reduxjs/toolkit"; import counterSlice from "./slices/counter/counterSlice"; const store = configureStore({ reducer: { counter: counterSlice.reducer }, }); export default store;
3 Use Provider to wrap the application
import '@/styles/globals.css' import type { AppProps } from 'next/app' import { Provider } from 'react-redux' import store from '@/store/index' export default function App({ Component, pageProps: { ...pageProps }, }: AppProps) { return ( <Provider store={store}> <main className='w-screen h-screen'> <Component {...pageProps} /> </main> </Provider> ) }
4 Create slice and add reducer
// ./src/store/slices/counter/counterSlice.js import { createSlice } from "@reduxjs/toolkit"; const counterSlice = createSlice({ name: "counter", initialState: { value:0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, }, }); export const { increment, decrement } = counterSlice.actions; export default counterSlice;
5 change states in components - useDispatch
, get state-useSelector
useDispatch
allows you dispatch action to modify states.
useSelector
allows you get state from redux store.
import React from "react"; import { useSelector, useDispatch } from "react-redux"; import { increment, decrement } from "./counterSlice"; const CounterComponent = () => { const count = useSelector((state) => state.counter.value); // get value from store const dispatch = useDispatch(); // dispatch will do actions to modify states in store return ( <div> <div>Count: {count}</div> <button onClick={() => dispatch(increment())}>Increment</button>{" "} {/* increment action */} <button onClick={() => dispatch(decrement())}>Decrement</button>{" "} {/* decrement action */} </div> ); }; export default CounterComponent;
END