: A state management system
for cross-component or app-wide state
: don't we have useContext already?
there should be only one data store
npm install redux
there should be no side effect code in the reducer function
(should not send a HTTP request or write/fetch something to/from a local storage)
redux-demo.js
const redux = require('redux');
// reducer function
// state can be any type(number, string ..., mostly object)
// should give state parameter a default value for the initial run
const counterReducer = (state = { counter : 0 }, action) => {
if (action.type === 'increment') {
return {
counter : state.counter + 1,
}
}
if (action.type === 'decrement') {
return {
counter : state.counter - 1,
}
}
return state;
};
const store = redux.createStore(counterReducer);
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState);
};
store.subscribe(counterSubscriber);
store.dispatch({ type : 'increment' });
store.dispatch({ type : 'decrement' });
node redux-demo.js로 실행
npm install redux react-redux
since redux can execute in any javascript environment
(which means it doesn't care or know about react)
also install the react-redux package
(a package that connects redux to react applications)
src/store/index.js
imprt { createStore } from 'redux';
const initialState = { counter : 0, showCounter : true}
const ⭐ counterReducer = (state = initialState, action) => {
// it won't merge the existing state, it will override
if (action.type === 'increment') {
return {
counter : state.counter + 1,
showCounter : state.showCounter
}
// state.counter ++;
// return state;
// should not return a modified original state
// ❗ (should not mutate the original state)
// should always create a new state and override it
}
if (action.type === 'increase') {
return {
counter : state.counter + action.amount,
showCounter : state.showCounter
}
}
if (action.type === 'decrement') {
return {
counter : state.counter - 1,
showCounter : state.showCounter
}
}
if (action.type === 'toggle') {
return {
showCounter : !state.showCounter,
counter : state.counter
}
}
return state;
}
// the dispatch type name can be overlapped
// if there are many states to manage, it can be complex
// => ✨ use redux toolkit!
const ⭐ store = createStore(⭐ counterReducer);
export default ⭐ store;
src/index.js
import { Provider } from 'react-redux';
import store from './store/index';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
⭐ <Provider store = {store} >
<App />
</Provider>
);
src/components/Counter.js
import { useSelector, useDispatch } from 'react-redux';
const Counter = () => {
⭐ const dispatch = useDispatch();
// useSelector
// whenever the data in the store changes
// it automatically updates its current state
// (the whole component(Counter component) will be re-executed
⭐ const counter = useSelector(state => state.counter);
⭐ const show = useSelector(state => state.showCounter);
const incrementHanlder = () => {
dispatch({ type : 'increment' })
};
const increaseHandler = () => {
dispatch({ type : 'increase', amount : 5 })
}
const decrementHanlder = () => {
dispatch({ type : 'decrement' })
};
const toggleCounterHandler = () => {
dispatch({ type : 'toggle' })
};
return (
<main className = {classes.counter}>
<h1>Redux Counter</h1>
{show && <div className = {classes.value}>{counter}</div>}
<div>
<button onClick = {incrementHandler}>Increment</button>
<button onClick = {increaseHandler}>Increase by 5</button>
<button onClick = {decrementHandler}>Decrement</button>
</div>
<button onClick = {toggleCounterHandler}>Toggle Counter</button>
</main>
)
}
export default Counter;
import { useSelector, useDispatch, ⭐ connect } from 'react-redux';
import { Component } from 'react';
class Counter extends Component {
incrementHandler() {
this.props.increment();
}
decrementHandler() {
this.props.decrement();
}
toggleCounterHandler() {}
render() {
return(
<main className = {classes.counter}>
<h1>Redux Counter</h1>
<div className = {classes.value}>{this.props.counter}</div>
<div>
<button onClick = {this.incrementHandler.bind(this)}>
Increment
</button>
<button onClick = {this.decrementHandler.bind(this)}>
Decrement
</button>
</div>
<button onClick = {this.toggleCounterHandler}>Toggle Counter</button>
</main>
)
}
}
⭐ // equivalent to useSelector
const mapStateToProps = state => {
return {
counter : state.counter
}
}
⭐ // equivalent to useDispatch
const mapDispatchToProps = dispatch => {
return {
increment : () => dispatch({ type : 'increment' })
decrement : () => dispatch({ type : 'decrement' })
}
}
export default ⭐ connect(mapStateToProps, mapDispatchToProps)(Counter);
// ❗ higher order component
// execute connect function, returns a new function
// and execute this returned, the new function as well
npm install @reduxjs/toolkit
src/store/index.js
import { createSlice, configureStore } from '@reduxjs/toolkit';
const initialCounterState = { value : 0, showCounter : true}
const counterSlice = ⭐ createSlice ({
name : 'counter',
intialState : initialCounterState,
reducers : {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.⭐ payload;
},
toggleCounter (state) {
state.showCounter = !state.showCounter;
}
}
});
// ❗ in reducers, we are now allowed to modify the original state
// ❗ the toolkit package will automaticallt create a new state with a modified state
// thus translating into immutable code
const initialAuthState = {
isAuthenticated : false
}
const authSlice = ⭐ createSlice({
name : 'authentication',
initialState : initialAuthState,
reducers : {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
}
}
});
const store = ⭐ configureStore({
reducer : { counter : counterSlice.reducer, auth : authSlice.reducer } ,
});
export const 🧨 counterActions = counterSlice.actions;
export const 🧨 authActions = authSlice.actions;
export default store;
src/components/Counter.js
import { 🧨 counterActions } from '../store/index';
import { useSelector, useDispatch } from 'react-redux';
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter.value);
const show = useSelector(state => state.counter.showCounter);
const incrementHanlder = () => {
dispatch(🧨 counterActions.increment());
// ⭐ when the counterActions.increment❗() executes,
// it returns a action type object
// ex. { type : SOME_UNIQUE_IDENTIFIER }
};
const increaseHandler = () => {
dispatch(🧨 counterActions.increase(10));
// ex. { type : SOME_UNIQUE_IDENTIFIER, payload : 10 }
// ⭐ payload property is automatically generated
}
const decrementHanlder = () => {
dispatch({ 🧨 counterActions.decrement() })
};
const toggleCounterHandler = () => {
dispatch({ 🧨 counterActions.toggleCounter() })
};
return (
<main className = {classes.counter}>
<h1>Redux Counter</h1>
{show && <div className = {classes.value}>{counter}</div>}
<div>
<button onClick = {incrementHandler}>Increment</button>
<button onClick = {increaseHandler}>Increase by 10</button>
<button onClick = {decrementHandler}>Decrement</button>
</div>
<button onClick = {toggleCounterHandler}>Toggle Counter</button>
</main>
)
}
export default Counter;
can split the code according to different roles
(counter.js / auth.js)
counter.js
import { createSlice } from '@reduxjs/toolkit';
const initialCounterState = { value : 0, showCounter : true}
const counterSlice = createSlice ({
name : 'counter',
intialState : initialCounterState,
reducers : {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter (state) {
state.showCounter = !state.showCounter;
}
}
});
export const 🧨 counterActions = counterSlice.actions;
export default ⭐ counterSlice.reducer;
auth.js
import { createSlice } from '@reduxjs/toolkit';
const initialAuthState = {
isAuthenticated : false
}
const authSlice = createSlice({
name : 'authentication',
initialState : initialAuthState,
reducers : {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
}
}
});
export const 🧨 authActions = authSlice.actions;
export default ⭐ authSlice.reducer;
index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counter';
import authReducer from './auth';
const store = ⭐ configureStore({
reducer : { counter : ⭐ counterReducer, auth : ⭐ authReducer } ,
});
export default store;