Mastering State Management: A Comprehensive Guide to Using Redux with Vanilla JavaScript

Mastering State Management: A Comprehensive Guide to Using Redux with Vanilla JavaScript

Redux is a predictable state container for JavaScript apps that can help you organize and manage your app's state in a more efficient and scalable way. In this post, we'll explore how to use Redux with vanilla JavaScript.

Step 00: Get Familiar with Basic Terminology

Store: In Redux, store is an object that holds the state of your application and provides an interface for dispatching actions and subscribing to state changes.

Action: In Redux, actions are objects that describe what has happened in an application. They typically contain a type field that specifies the type of action being performed, as well as any additional data necessary to update the state.

Dispatch: In Redux, dispatch is a function provided by the store that allows you to dispatch actions to update the state of your application. When you call dispatch and pass in an action object, Redux will pass that object to the store's reducer function, which will then update the application state based on the type and payload of the action.

Reducer: In Redux, a reducer is a pure function that takes in the current state and an action, and returns a new state. The reducer is responsible for updating the state of your application in response to dispatched actions and is the central piece of your Redux state management code.

Step 01: Including Redux with a CDN Link

<html>
  <head>
    <title>My Redux App</title>
    <script src=" https://unpkg.com/redux@latest/dist/redux.js"></script>
  </head>
  <body>
    <!-- Your app's content goes here -->
  </body>
</html>

Let's Get Started with Redux! First, we need to include the Redux CDN link by adding a script tag with the below link inside the head tag of your HTML file.

CDN Link: https://unpkg.com/redux@latest/dist/redux.js

This will make Redux available as a window.Redux global variable.

Step 02: Creating Action identifiers

const INCREMENT = 'counter/increment'
const DECREMENT = 'counter/decrement'

Before creating actions in Redux, it is a common practice to define action identifiers, which are unique labels that identify the specific action being performed.

Defining action identifiers helps to prevent typos or errors when dispatching actions in your application, making your code more maintainable and easier to debug. You can define action identifiers as constants or variables, depending on your preference.

When defining action identifiers in Redux, it is often helpful to include the name of the feature or component that the action relates to. This not only helps to avoid naming conflicts but also makes it easier to identify which parts of the application are being affected by certain actions.

Step 03: Defining action creators

const incrementByValue = (id, value) => {
  return {
    type: INCREMENT,
    payload: {
      id,
      value,
    },
  };
};

As your Redux-powered application grows in complexity, you'll likely find yourself writing more and more actions to update the application state. This can quickly become cumbersome and error-prone, especially if you have to manually create action objects every time you need to dispatch an action.

In Redux, action creators are functions that create and return action objects. They provide a convenient way to define and organize actions in your code and can help reduce the amount of boilerplate code you need to write to dispatch actions.

Action creators typically take in some arguments, which are used to construct the action object. These arguments can include data or metadata needed to update the state of your application. Once an action creator is called and returns an action object, the object can be dispatched to the Redux store to update the state.

Step 04: Defining Initial State

const initialState = {
  count: 0
};

The initial state is the starting point for the state of your application and represents the values of the state properties before any actions have been dispatched.

Step 05: Defining Reducer Function

function counterReducer(state = initialState, action) {
  // reducer logic here
}

Reducers are defined using the switch statement, where each case represents a different action type. When an action is dispatched, Redux will call the reducer function and pass in the current state and the action object. The reducer then evaluates the action type and performs any necessary updates to the state based on the action payload.

It's important to note that reducers must be pure functions, meaning that they must always return a new state object and should not modify the original state object. This ensures that the state of your application remains predictable and that Redux's time-travel debugging and other features work as expected.

Reducers can be combined using the combineReducers function, which allows you to create a single reducer function from multiple smaller reducers. This can help you organize your Redux code and make it more modular and easier to maintain.

Step 06: Creating the store

// redux store
const store = Redux.createStore(rootReducer);

The store is created using the createStore function provided by Redux.

The store is the central hub of your Redux application and is responsible for managing the state of your application and ensuring that updates are performed predictably and consistently. When an action is dispatched, the store passes the action to the reducer function, which updates the state based on the action payload. The updated state is then stored in the store, and any subscribers to the store are notified of the state change.

The store provides several methods for interacting with the state, including getState, which returns the current state of the store, dispatch, which is used to dispatch actions to update the state, and subscribe, which is used to register callback functions that will be called whenever the state changes.

Step 07: Subscribe to the store

// Create the store
const store = Redux.createStore(reducer);

// Define a listener function
function listener() {
  console.log('New state:', store.getState());
}

// Subscribe to the store
store.subscribe(listener);

In Redux, you can subscribe to the store to be notified whenever the state changes. To subscribe to the store, we use the subscribe method of the store object, passing in the listener function as an argument. This adds the listener function to an array of listeners that will be notified whenever the state changes.

Whenever an action is dispatched that causes the state to change, the store updates its state and then calls each listener function in the array, passing in the current state as an argument.

You can unsubscribe from the store by calling the function returned by the subscribe method, like this:

const unsubscribe = store.subscribe(listener);

// Later, to unsubscribe:
unsubscribe();

This removes the listener function from the array of listeners, so it will no longer be called when the state changes.

Step 08: Dispatch an action

store.dispatch(incrementByValue(currentId, currentValue));

The dispatch function can be used in various parts of your code, including in response to user events, response to network requests, or in response to changes in the application's internal state. By dispatching actions to update the state, you can ensure that the state of your application remains predictable and consistent, as all state updates are performed through a centralized mechanism.

One important thing to note is that dispatch is a synchronous function, meaning that it returns immediately after dispatching the action. This means that any side effects, such as API calls or other asynchronous operations, should be handled in middleware or the action creator, rather than in the dispatch call itself.

Other useful APIs and methods

store.getState(): Returns the current state tree of your application. It is equal to the last value returned by the store's reducer.

combineReducers(): As your app grows more complex, you'll want to split your reducing functions into separate functions, each managing independent parts of the state.

The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
  potato: {
    // ... potatoes, and other state managed by the potatoReducer ...
  },
  tomato: {
    // ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
  }
}