State management is a crucial aspect of building complex applications in React. It allows you to manage and share data between different components efficiently. Let’s start with the basics:

In React, each component can have its own local state. State is an object that represents how a component should render and behave

Basics

Reducers

A reducer is simply a function that allows us to centralize state updates in a component. useReducer(reducer, initialArg, init?)

Parameters

  • reducer:
    • ➡️ The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types.
  • initialArg:
    • ➡️ The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next init argument.
  • optional init: - ➡️ The initializer function that should return the initial state. If it’s not specified, the initial state is set to initialArg. Otherwise, the initial state is set to the result of calling init(initialArg). Syntax
import { useReducer } from 'react';
 
function reducer(state, action) {
  // ...
}
 
function MyComponent() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });
  // ...

Returns

useReducer returns an array with exactly two values:

  1. The current state. During the first render, it’s set to init(initialArg) or initialArg (if there’s no init).
  2. The dispatch function that lets you update the state to a different value and trigger a re-render.

dispatch function 

The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function:

const [state, dispatch] = useReducer(reducer, { age: 42 });
function handleClick() {
	dispatch({ type: 'incremented_age' });  // ...

React will set the next state to the result of calling the reducer function you’ve provided with the current state and the action you’ve passed to dispatch.

Dispatch with payload

Sometimes you need a given payload to be sent with your dispatch

interface Task {
  id: number
  title: string
}
interface AddTask {
  type: "ADD"
  task: Task
}
 
interface DeleteTask {
  type: "DELETE"
  taskId: number
}
 
type Action = AddTask | DeleteTask
 
export default function taskListReducer(tasks: Task[], actions: Action) {
  if (actions.type === "ADD")
    return [
      actions.task, // new task to be added
      ...tasks,
    ]
 
  if (actions.type === "DELETE") return tasks.filter((t) => t.id !== actions.taskId) // the task to be removed
  return tasks // for invalid actions.
}
const [tasks, dispatch] = useReducer(taskListReducer, [])
 
return (
    <>
      <div className='p-20'>
        <Button
          onClick={() =>
         
            dispatch({ type: 'ADD', task: { id: Math.random(), title: 'New Task' } })
 
          }
        >
          Add Task
        </Button>
        ....
Example2
interface LoginAction {
  type: "LOGIN"
  username: string
}
interface LogoutAction {
  type: "LOGOUT"
}
type AuthAction = LoginAction | LogoutAction
export default function loginReducer(state: string, actions: AuthAction) {
  if (actions.type === "LOGIN") return actions.username
  if (actions.type === "LOGOUT") return ""
  return state
}
const LoginStatus = () => {
  const [user, dispatch] = useReducer(loginReducer, "")
 
  if (user)
    return (
      <>
        <div className="p-20 text-3xl">
          <span className="mx-10 text-3xl">{user}</span>
          <a onClick={() => dispatch({ type: "LOGOUT" })} href="#">
            Logout
          </a>
        </div>
      </>
    )
  return (
    <div className="p-20 text-3xl">
      {/*Adding a payload(the data we are going to use in our reducer function) */}
      <a onClick={() => dispatch({ type: "LOGIN", username: "Ilunga.Daniel.Gisa" })} href="#">
        Login
      </a>
    </div>
  )
}

React Context API

Context API is a built-in mechanism in React to manage state at a global level. It allows you to share data between components without having to pass props down through the entire component tree.

What is context? Context is like state, but instead of being confined to a component, it’s global to your application. It’s application-level state. This is dangerous. Avoid using context until you have to use it.

Allows sharing data without passing it down through many components in the middle

The reducer hook and the state are both ways to maintain local state in the components,

Redux

Redux is a popular state management library for React applications. It enforces a strict unidirectional data flow and provides a central store for your application’s state.

Redux is a predictable state container for JavaScript apps

Redux API

Redux is really small it simply has few methods attached to it .

  • ➡️ Apply Middleware
  • ➡️ Compose
  • ➡️ Combine Reducers
  • ➡️ Bind action creator
  • ➡️ Create Store
    • dispatch: ƒ dispatch(action)
    • getState: ƒ getState()
    • replaceReducer: ƒ replaceReducer(nextReducer)
    • subscribe: ƒ subscribe(listener)

A reducer is simply a function that takes in a state, and the things that happened and only one thing comes out of it, a new state

Compose

The compose function takes multiple functions as arguments and returns a new function that represents the composition of these functions. In Redux, it’s commonly used to apply multiple store enhancers or middleware to the store creation process.

Create Store

A function that takes a reducer and creates you main state returns you functions to interact with your tore

  • dispatch: ƒ dispatch(action)
    • Sends actions into the reducer to do mutations to the current state of the application
  • getState: ƒ getState()
    • get the state of our store
  • replaceReducer: ƒ replaceReducer(nextReducer)
    • Swaps out the reducer
  • subscribe: ƒ subscribe(listener)
    • Subscribes to the store and when something changes, then do domething!

Bind action creator

Binds an action, then returns a new object with the same keys, but each action creator is wrapped in a dispatch call. This makes it easier to invoke the action creators directly, and the resulting functions automatically dispatch the actions.

import { bindActionCreators } from "redux"
import store from "./store" // Assume you have a Redux store
import { increment, decrement } from "./actions" // Assume you have action creators
 
// Manually binding action creators
const manuallyBoundActionCreators = {
  increment: () => store.dispatch(increment()),
  decrement: () => store.dispatch(decrement()),
}
 
// Using bindActionCreators
const boundActionCreators = bindActionCreators({ increment, decrement }, store.dispatch)
 
// Now you can directly call the action creators with the store's dispatch
boundActionCreators.increment()
boundActionCreators.decrement()

Core Concepts

Redux is based on three core principles:

  1. Store: A single, immutable source of truth for your application’s state.
  2. Actions: Plain JavaScript objects that describe a change in the application’s state.
  3. Reducers: Functions that specify how the state changes in response to actions.

Actions

Synchronous Actions

Actions are plain JavaScript objects that describe events or user interactions in your application. They represent what happened and the data associated with the event. An action object typically includes two properties:

  • type:
    • ➡️ A string that describes the type of action. It’s a unique identifier for the action.
  • payload:
    • ➡️ An optional property that carries data related to the action. This can be any valid JavaScript value like an object, string, or number.

Here’s an example of an action object:

const incrementAction = {
  type: "INCREMENT",
  payload: 1,
}

Actions serve as a way to communicate changes in your application, and they are dispatched to the Redux store, where reducers respond to them

Async Actions

Asynchronous API calls to fetch data from an endpoint and use the data in your application

Reducers

specify how the app’s state changes in response to actions sent to the store function that accepts state and action as arguments and returns the next state of the application

(previousState,action) => newState

Store

one store for the entire application

  • It has the following responsibilities
    • Holds application state
    • Allows access to state via getState()
    • Allow sate to be updated via Dispatch(action)
    • Register listeners via subscribe(listener )
    • Handle unregistering of listeners via the function returned by subscribe(listener)

Redux Store Methods

replaceReducer

Takes one reducer and replace it with another one

The Redux store exposes a replaceReducer function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves:

getState

This simply gets a state

Middleware

is the suggested way to extend Redux with custom functionality Provides a third party extension point between dispatching an action, and the momenet it reaches the reducer

  • We use middleware for :
    • Logging,
    • Crash reporting
    • performing asynchronous tasks etc…