Summary
- Used for state management across multiple components.
- Uses pure functional programming concepts to implement
ref and good tutorial about redux
Terminology
- copy diagram from here
Actions(events names that change store) and Action creators
-
Actions
You can think of an action as an event that describes something that happened in the application.
const addTodoAction = { type: 'todos/todoAdded', payload: 'Buy milk' }
-
Action creators
Action creators are functions that create actions.
// getUser is the action creator export const getUser = user => ({ type: 'GET_USER', payload: user, }); //the action is { type: 'GET_USER', payload: user, }
Reducers(functions that mutate store)
(event listener for the actions) a. A reducer is a function that receives the current state and an action object, decides how to update the state if necessary, and returns the new state: (state, action) => newState.
b. You can think of a reducer as an event listener which handles events based on the received action (event) type.
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/incremented') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
Store
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
Dispatch(api to mutate)
The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object.
store.dispatch({ type: 'counter/incremented' }) # the way UI interacts or mutates store
console.log(store.getState())
// {value: 1}
dispatch – for dispatching the actions to the reducers, this is the only way to mutate the state.
Selectors(access state)
Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
Components
-
Presentations Components
Components that describe the look of your data and render the information given to them.
-
Container Components
they connect our ‘presentations’ components to Redux to get access to the store.
-
mapStateToProps vs mapDispatchToProps
-
mapStateToProps is a function that you would use to provide the store data to your component,
-
mapDispatchToProps is something that you will use to provide the action creators as props to your component.
function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return { addTodo: bindActionCreators(addTodo, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(Todos);
-
Complete example
import { createStore } from "redux";
// initialState
const initialState = {
user: []
products: [],
cart: [],
fav: []
}
// reducer
function cartReducer(state = initialState, action) {
switch(action.type) {
case 'ADD_TOCART': {
return {...state, cart: [...state.cart, action.payload]}
}
default: {
return state;
}
}
}
// creating the store by invoking createStore method by passing the reducer
const store = createStore(cartReducer);
// subscribing to state changes, usually we use "React Redux" view binding library instead of directly subscribing.
store.subscribe(() => console.log(store.getState()));
// dispatching action
store.dispatch({
type: 'ADD_TO_CART'
payload: {
id: 1,
quantity: 2
}
});
// dispatching the above action will change the state of application and console will log the current state.
/*
{
user: []
products: [],
cart: [{
id: 1,
quantity: 2
}],
fav: []
}
*/
Data Flow Diagrams
createSlice
Created to make it easier to write redux constructs. createSlice comes with the package redux-toolkit. A slice is a function that contains your store and reducer functions used to modify store data.
-
It is a function that accepts an initial state, an object full of reducer functions, and a “slice name”, and automatically generates action creators and action types that correspond to the reducers and state.
-
Within createSlice, synchronous requests made to the store are handled in the reducers object while extraReducers handles asynchronous requests, which is our main focus.
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState = { value: 0 } as CounterState
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value++
},
decrement(state) {
state.value--
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
Thunk
Thunk intercepts and manages actions. ref
- The word “thunk” is a programming term that means a piece of code that does some delayed work. Rather than execute some logic now, we can write a function body or code that can be used to perform the work later.
- For Redux specifically, “thunks” are a pattern of writing functions with logic inside that can interact with a Redux store’s dispatch and getState methods(while accessing or mutating).
- Thunks are the standard approach for writing async logic in Redux apps
- Using thunks requires the redux-thunk middleware to be added to the Redux store as part of its configuration.
- A thunk function may contain any arbitrary logic, sync or async, and can call dispatch or getState at any time.
- Redux middleware were designed to enable writing logic that has side effects.
A thunk function is a function that accepts two arguments: the Redux store dispatch method, and the Redux store getState method. Thunk functions are not directly called by application code.
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
}
store.dispatch(thunkFunction)
Why Use Thunks?
Thunk returns a function instead of action. Middlware calls this function injecting dispatch and useState/getState. If reducer requires async logic.
Thunks allow us to write additional Redux-related logic separate from a UI layer. This logic can include side effects, such as async requests or generating random values, as well as logic that requires dispatching multiple actions or access to the Redux store state.
- Moving complex logic out of components
- Making async requests or other async logic
- Writing logic that needs to dispatch multiple actions in a row or over time
- Writing logic that needs access to getState to make decisions or include other state values in an action
Install and Enable Thunk
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers/index'
const store = createStore(rootReducer, applyMiddleware(thunk))
createAsyncThunk
A function that accepts a Redux action type string and a callback function that should return a promise.
It generates promise lifecycle action types based on the action type prefix that you pass in, and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise.
-
redux cannot handle asycn logic
with Redux Thunk’s middleware being the most popular package.
-
Example
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' const initialState = { entities: [], loading: false, } const getPosts = createAsyncThunk( //action type string 'posts/getPosts', // callback function async (thunkAPI) => { const res = await fetch('https://jsonplaceholder.typicode.com/posts').then( (data) => data.json() ) return res }) export const postSlice = createSlice({ name: 'posts', initialState, reducers: {}, extraReducers: {}, }) export const postReducer = postSlice.reducer
posts/getPosts is the action type string in this case. Whenever this function is dispatched from a component within our application, createAsyncThunk generates promise lifecycle action types using this string as a prefix: these lifecycle action types are called automatically according to the status of the promise.
pending: posts/getPosts/pending fulfilled: posts/getPosts/fulfilled rejected: posts/getPosts/rejected
using extraReducer to change the state based on the promise life cycle hooks
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' const initialState = { entities: [], loading: false, } const getPosts = createAsyncThunk( 'posts/getPosts', async (thunkAPI) => { const res = await fetch('https://jsonplaceholder.typicode.com/posts').then( (data) => data.json() ) return res }) export const postSlice = createSlice({ name: 'posts', initialState, reducers: {}, extraReducers: { [getPosts.pending]: (state) => { state.loading = true }, [getPosts.fulfilled]: (state, { payload }) => { state.loading = false state.entities = payload }, [getPosts.rejected]: (state) => { state.loading = false }, }, }) export const postReducer = postSlice.reducer
useSelector
This is used to select part of the redux state. Once set, the returned value is always is updated or changed whenever state changes.
import useSelector from "react-redux";
const amount = useSelector(state => state.amount);
mapStateToProps vs mapDispatchToProps
mapStateToProps is a function that you would use to provide the store data to your component, whereas mapDispatchToProps is something that you will use to provide the action creators as props to your component.
mapStateToProps
const mapStateToProps = (state) =>
return things: state.things
;
mapDispatchToProps
As implied in its name, this function directs the dispatching or sending of an action by pointing it to an action creator. For example:
const mapDispatchToProps = () =>
return
addThing: addThing,
doAnotherThing: doAnotherThing
The action creator is made available to the component as a prop, which is usually tied to an event handler function contained in the component:
handleOnClick()
this.props.addThing();
;
However, returning the action creator is only one part. We also want the send that returned action to the store. How do we do that? We use Redux’s bindActionCreators(). To implement it, we:
import bindActionCreators from 'redux';
...
const mapDispatchToProps = (dispatch) =>
return bindActionCreators(
addThing: addThing,
doAnotherThing: doAnotherThing
, dispatch);
;