Summary

  1. Used for state management across multiple components.
  2. 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

    ref

    1. mapStateToProps is a function that you would use to provide the store data to your component,

    2. 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

ref

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.

  1. 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.

  2. 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

  1. 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.
  2. 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).
  3. Thunks are the standard approach for writing async logic in Redux apps
  4. Using thunks requires the redux-thunk middleware to be added to the Redux store as part of its configuration.
  5. A thunk function may contain any arbitrary logic, sync or async, and can call dispatch or getState at any time.
  6. 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)

ref

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

    ref

      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);
;