Redux is a library for managing and updating application state library which is one-way data flow
Redux only have one store which can be an object and be used globally
Components will subscribe to the store and the store will notify to its component when changes
Reducer is used to take the input and update the store
When component want to make change to the store, it needs to dispatch the action which is described the the changes, and reducer take the action as a input and change the store
Reducer
Contain different business logics and return different states based on the actions.
Should be a pure function (not contain other data sources, such as api)
If we involve the the business logic related to async function (such api call), we need saga to implement it instead of doing it in the reducer, since reducer should be a pure function, also the promise function will be returned instead of the resolve value if the reducer becomes a async function
Flow
Pre-Condition
npm install redux-saga
Link With reducer
We create rootSaga , apply the saga middleware with reducer to the store and run the rootSaga
// index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import Count from "./redux/reducer";
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./saga";
const sagaMiddleWare = createSagaMiddleware();
const store = createStore(Count, applyMiddleware(sagaMiddleWare));
sagaMiddleWare.run(rootSaga);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
RootSaga
Divided into 2 parts - worker and watcher
Worker: Containing logic to implementing the async function ( use call method) and trigger the existing action ( use put method)
Watcher: Monitoring the specific action, if the type of specific action is called, the function of the worker will be triggered, monitoring function mainly contain 2 types : takeLatest and takeEvery. takeLatest: If the previous worker still not finished, the new worker will start and the previous one will be cancelled. takeEvery: even the previous worker is not finished, the new worker can be triggered, and the previous worker is still running at the same time
The action is structured, contains payload and type,
createSlice automatically generates a slice reducer with corresponding action creators and action types. so longer need to map the reducer and action by type
As the state is immutable, if you need to change the value, you must need to return new object and do the logic on it
function plainJsReducer(state, action) {
// Add 3 points to Ravenclaw,
// when the name is stored in a variable
const key = "ravenclaw";
return {
...state, // copy state
houses: {
...state.houses, // copy houses
[key]: { // update one specific house (using Computed Property syntax)
...state.houses[key], // copy that specific house's properties
points: state.houses[key].points + 3 // update its `points` property
}
}
}
}
By using redux-toolkit, since its reducer function already include immer, so you can change the value with user-friendly syntax without mutating the state
function immerifiedReducer(state, action) {
const key = "ravenclaw";
// produce takes the existing state, and a function
// It'll call the function with a "draft" version of the state
return produce(state, draft => {
// Modify the draft however you want
draft.houses[key].points += 3;
// The modified draft will be
// returned automatically.
// No need to return anything.
});
}
Implementation
Main (Group the slices together)
import { combineReducers } from "@reduxjs/toolkit";
import roomReducer from "./slice/roomSlice";
import userReducer from "./slice/userSlice";
const rootReducer = combineReducers({
roomInfo: roomReducer,
userInfo: userReducer,
});
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
Slice (Define reducer and action and their relationship)
import React from "react";
import ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./pages/dashboard";
import Index from "./pages";
import { Provider } from "react-redux";
import rootReducer from "./reducer";
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({ reducer: rootReducer });
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" component={Index} />
</Switch>
</div>
</Router>
</Provider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();