Module: Redux Fundamentals

Redux Core Concepts

Redux Core Concepts

Redux is a predictable state container for JavaScript apps. It helps you manage application state in a centralized and organized way. Here's a breakdown of the core concepts:

1. Store

  • What it is: The single source of truth for your application's state. It holds the entire state tree of your application.

  • Responsibility:

    • Holds the application's state.
    • Allows access to the state via getState().
    • Allows state to be updated via dispatch(action).
    • Handles updates to the state by running reducers.
  • Creation: Created using createStore(reducer, [preloadedState], [enhancers]).

  • Example:

    import { createStore } from 'redux';
    
    // A simple reducer (explained later)
    const reducer = (state = { count: 0 }, action) => {
      return state;
    };
    
    const store = createStore(reducer);
    
    console.log(store.getState()); // { count: 0 }
    

2. Actions

  • What it is: Plain JavaScript objects that describe something that happened. They are the only way to trigger a state change.

  • Structure: Must have a type property, which indicates the type of action being performed. Can optionally include a payload property containing data related to the action.

  • Responsibility: Signal the intention to change the state.

  • Example:

    // Action creator (a function that returns an action)
    const increment = () => {
      return {
        type: 'INCREMENT',
      };
    };
    
    const decrement = (amount) => {
      return {
        type: 'DECREMENT',
        payload: amount,
      };
    };
    
    // Dispatching actions
    store.dispatch(increment());
    store.dispatch(decrement(5));
    

3. Reducers

  • What it is: Pure functions that take the current state and an action, and return the next state.

  • Purity: Crucially, reducers must be pure functions. This means:

    • They should not mutate the existing state. Instead, they should return a new state object.
    • They should not have any side effects (e.g., making API calls, logging).
    • Given the same input (state and action), they should always return the same output (next state).
  • Responsibility: Specify how the application's state changes in response to actions.

  • Example:

    const reducer = (state = { count: 0 }, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return { ...state, count: state.count + 1 }; // Create a new state object
        case 'DECREMENT':
          return { ...state, count: state.count - action.payload }; // Create a new state object
        default:
          return state; // Return the current state if the action is unknown
      }
    };
    

4. Dispatch

  • What it is: A function provided by the Redux store.
  • Responsibility: Used to send actions to the store. The store then passes the action to the reducer.
  • Usage: store.dispatch(action)
  • Example: (See Action examples above)

5. Subscription

  • What it is: A way to listen for changes to the Redux store.

  • Responsibility: Allows components to re-render or perform other actions when the state changes.

  • Usage: store.subscribe(listener)

  • Example:

    const unsubscribe = store.subscribe(() => {
      console.log('State changed:', store.getState());
    });
    
    // Later, to stop listening:
    unsubscribe();
    

Data Flow

  1. Component dispatches an action: A component initiates a state change by calling store.dispatch(action).
  2. Store passes the action to the reducer: The store receives the action and passes it to the reducer function.
  3. Reducer calculates the next state: The reducer determines how the state should change based on the action and returns a new state object.
  4. Store updates the state: The store replaces the old state with the new state returned by the reducer.
  5. Subscribed components re-render: Any components subscribed to the store are notified of the state change and can re-render to reflect the updated data.

Combining Reducers

For larger applications, it's common to split your state into smaller, independent pieces and manage them with separate reducers. combineReducers from Redux helps with this.

  • Purpose: Combines multiple reducers into a single reducer.

  • Structure: Takes an object where the keys represent the slice of state each reducer manages, and the values are the reducers themselves.

  • Example:

    const userReducer = (state = { name: '' }, action) => {
      // ... handle user-related actions
      return state;
    };
    
    const productReducer = (state = { items: [] }, action) => {
      // ... handle product-related actions
      return state;
    };
    
    const rootReducer = combineReducers({
      user: userReducer,
      products: productReducer,
    });
    
    const store = createStore(rootReducer);
    

Now, store.getState() will return an object with user and products properties, each managed by its respective reducer.

These core concepts form the foundation of Redux. Understanding them is crucial for building scalable and maintainable applications with predictable state management. Libraries like React-Redux provide convenient hooks and components to connect your React components to the Redux store.