#
React (Redux Toolkit)
#
Install
npm install @reduxjs/toolkit
npm install react-redux
You can remove redux from packages as it is already included.
Redux-toolkit will update the state in an immutable way.
#
createSlice
import { createSlice } from '@reduxjs/toolkit';
const initialState = { counter: 0, showCounter: true }
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.counter++;
},
// action is the extra data
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
}
}
});
const store = createStore(counterSlice.reducer);
#
configureStore
const store = configureStore({
reducer: counterSlice.reducer
});
// or create map of reducers
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
To dispatch actions. We don't access reducer methods but use methods (action creators).
export const counterAcjtions = counterSlice.actions.toggleCounter
// in component:
import { counterActions } from '../store/index';
import { useDispatch } from 'react-redux';
const Counter = () => {
const dispatch = useDispatch();
const incrementHandler = () => {
dispatch(counterActions.increment());
}
const increaseHandler = () => {
dispatch(counterActions.increase(5)); // { type: IDENTIFIER, payload: 5 }
}
}
import from 'react-redux';
import store from './store/index';
root.render(
#
Working with multiple slices
const initialAuthState = { isAuthenticated: false }
const authSlice = createSlice({
name: 'auth',
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 authActions = authSlice.actions;
const counter = useSelector((state) => state.counter.counter);
import { useSelector } from 'react-redux';
function App() {
const isAuth = useSelector(state => state.auth.isAuthenticated);
return (
{!isAuth && <Login/>}
{isAuth && <UserProfile/>}
)
}
import { useDispatch } from 'react-redux';
import { authActions } from '../store/index';
const Auth = () => {
const dispatch = useDispatch();
const loginHandler = (event) => {
event.preventDefault();
dispatch(authActions.login());
}
}
#
Splitting code
Create a file for each slice
store |- auth.js |- counter.js |- index.js
In the non index.js files, export the reducer only and the actions.
e.g. export default counterSlice.reducer
and export const counterAction = counterSlice.actions;
In index.js:
import counterReducer from './counter
const store = configureStore({
reducer: { counter: counterReducer }
});
#
Using async
- Your reducer functions must be pure, side-effect free, and synchronous. So your reducer functions should take some input in the case of the Redux reducer, the old state and the action, and then produce some output.
- Put side effects code in the component (useEffect()) OR in our own action creator functions. Cannot use reducers.
#
Use useEffect()
const cart = useSelector((state) => state.cart);
useEffect(() => {
const response = await fetch('https://react-http-aaaa.firebaseio.com/cart.json',
{
method: 'PUT',
body: JSON.stringify(cart)
}
);
if (!response.ok) {
throw new Error('');
}
const responseData = await response.json();
}, [cart]);
We face one problem when using useEffect the way we currently do it: It will execute when our app starts. It's a problem because this will send the initial (i.e. empty) cart to our backend and overwrite any data stored there.
To fix, add a variable outside so it does not change when the component is reloaded:
let isInitial = true;
function App() {
...
useEffect(() => {
...
if (isInitial) {
isInitial = false;
return;
}
})
}
#
Use action creator thunk
A thunk is a function that delays an action until later. We could write an action creator as a thunk, which does not immediately return the action object, but which instead, returns another function which eventually returns the action. So that we can run some other code before we then dispatch the actual action object that we did want to create.
export const sendCartData = (cartData) => {
return async (dispatch) => {
// perform any side-effects/async stuff
dispatch(...);
};
};
useEffect(() => {
...
if (isInitial) {
isInitial = false;
return;
}
dispatch(sendCartData(cart));
})