PRO 30% Free • Stripe ($15) & Razorpay (₹899)

Building a Redux Store from Scratch Using Vanilla JavaScript

Top Course Staff · 4 min read · Web development · PRO · 30% Free Preview Available

Building a Redux Store from Scratch Using Vanilla JavaScript

Who is this article for?

Though thereduxpackage provides thecreateStore()method for us, examining how this powerful object can be created using vanillaJavaScriptwill help illuminate how Redux works under the hood. This article is for learners who want to solidify their understanding of the Redux store.

It is assumed that you have some familiarity with the following Redux-related terms and concepts:

  • The one-way data-flow model (state → view → actions → state)
  • Reducer functions
  • Action objects
  • ThecreateStore()function (by thereduxpackage)
  • Thestoreobject andits three main methods

Visitour course on Reduxto learn about or refresh yourself with these terms and concepts.

What is the Redux store and how is it used?

Reduxis a state-management library centered around a single, powerful object called thestore. This one object is responsible for holding the entire application’s state, receivingaction objectsand then executing state changes based on the type of the action received, and informing (executing)listener functionswhen such changes occur.

To help create thisstoreobject, the Redux library provides thecreateStore()function. This function accepts a reducer function as an argument and returns astoreobject with three essential methods:

  • store.getState(): for retrieving the current state value held by thestore
  • store.dispatch(action): for triggering changes to the state, given an action object
  • store.subscribe(listener): for registering listener functions to be called when state changes occur

All of this can be seen in the example below which implements a simple counting application:

Setting up Redux in a vanilla JavaScript project

import { createStore } from 'redux';
const countReducer = (state = 0, action) => {  switch (action.type) {      case 'increment':        return state + 1;    case 'decrement':        return state - 1;    default:        return state;    }}const store = createStore(countReducer);
const render = () => {  document.getElementById('count').text = store.getState(); // Display the current state.}render(); // Render once with the initial state of 0.
store.subscribe(render); // Re-render on state changes.
document.getElementById('plusButton').addEventListener('click', () => {    store.dispatch({ type: 'increment' }); // Request a state change.});
Copy to clipboard
import { createStore } from 'redux';
const countReducer = (state = 0, action) => {  switch (action.type) {      case 'increment':        return state + 1;    case 'decrement':        return state - 1;    default:        return state;    }}const store = createStore(countReducer);
const render = () => {  document.getElementById('count').text = store.getState(); // Display the current state.}render(); // Render once with the initial state of 0.
store.subscribe(render); // Re-render on state changes.
document.getElementById('plusButton').addEventListener('click', () => {    store.dispatch({ type: 'increment' }); // Request a state change.});

With this working knowledge of how to use thecreateStore()function and thestoremethods, we can begin to write the outline of this function:

const createStore = (reducer) => {  const getState = () => {};  const dispatch = () => {};  const subscribe = () => {};  return { getState, dispatch, subscribe };}
Copy to clipboard
const createStore = (reducer) => {  const getState = () => {};  const dispatch = () => {};  const subscribe = () => {};  return { getState, dispatch, subscribe };}

Above, we definecreateStore()simply as a function with areducerargument that returns an object containing three methods:getState(),dispatch(), andsubscribe().

Holding the current state of the application

Let’s now turn our attention to the primary responsibility of thestore: to hold the current state of the application and to provide access to this value via thestore.getState()method. Implementing this behavior is as simple as storing an encapsulated variable (we can call itstate) within the function and returning it withstore.getState():

const createStore = (reducer) => {  let state;  const getState = () => state;  const dispatch = () => {};  const subscribe = () => {};  return { getState, dispatch, subscribe };}
Copy to clipboard
const createStore = (reducer) => {  let state;  const getState = () => state;  const dispatch = () => {};  const subscribe = () => {};  return { getState, dispatch, subscribe };}

Hiding thestatebehind theAPIof thestoreavoids common dangers associated with having global variables:

  • Polluting the global namespace increases the chances of naming collisions.
  • Granting variable access to parts of an application while limiting it to others is impossible.
  • Debugging is difficult when a variable is referenced in many parts of the application.

Redux solves these problems by requiring the programmer to use only thestoremethods to access the state.

Managing listener functions

The state of thestorewill likely change many times and the features of the application must be notified when those changes occur. Thestore.subscribe()method allows you to subscribe callback functions, calledlisteners, to be executed when the state data changes. As in the first example, functions that render the view-layer are often subscribed to thestoreand usestore.getState()to get the most up-to-date state data.

Any number of listeners may be subscribed to thestoreat once so an array is maintained by thestoreand thesubscribe()method adds provided listeners to that array.

const createStore = (reducer) => {  let state;  let listeners = [];
  const getState = () => state;
  const dispatch = () => {};
  const subscribe = (listener) => {    listeners.push(listener);    return () => {        listeners = listeners.filter(l => l !== listener)      }  };
  return { getState, dispatch, subscribe };}
Copy to clipboard
const createStore = (reducer) => {  let state;  let listeners = [];
  const getState = () => state;
  const dispatch = () => {};
  const subscribe = (listener) => {    listeners.push(listener);    return () => {        listeners = listeners.filter(l => l !== listener)      }  };
  return { getState, dispatch, subscribe };}

Also notice that thesubscribe()method returns a function. If you no longer want the given listener to be executed in response to state changes, this function can be saved and called tounsubscribethe given listener. For example:

const unsubscribe = store.subscribe(render); // Subscribes `render` to the store.
// somewhere else in the program...unsubscribe(); // Unsubscribes `render` from the store.
Copy to clipboard
const unsubscribe = store.subscribe(render); // Subscribes `render` to the store.
// somewhere else in the program...unsubscribe(); // Unsubscribes `render` from the store.

Handling incoming actions

Redux ensures that the state is maintained reliably by requiring the programmer to dispatch actions to the store if they wish to update the state. Thestore.dispatch()function accepts anactionobject as an argument and calculates the nextstatevalue by calling thereducer()with the currentstateand theaction:

const createStore = (reducer) => {  let state;  let listeners = [];    const getState = () => state;
  const dispatch = (action) => {    state = reducer(state, action);    listeners.forEach(listener => listener());  };
  const subscribe = (listener) => {    listeners.push(listener);    return () => {        listeners = listeners.filter(l => l !== listener)      }  };    dispatch({});  return { getState, dispatch, subscribe };}
Copy to clipboard
const createStore = (reducer) => {  let state;  let listeners = [];    const getState = () => state;
  const dispatch = (action) => {    state = reducer(state, action);    listeners.forEach(listener => listener());  };
  const subscribe = (listener) => {    listeners.push(listener);    return () => {        listeners = listeners.filter(l => l !== listener)      }  };    dispatch({});  return { getState, dispatch, subscribe };}

Each listener that has been subscribed to thestore(stored in thelistenersarray) is then executed. Notice that the state is not passed directly to these listeners. It is up to each listener to usestore.getState()to get the most up-to-date data.

Finally, notice that before thestoreobject is returned, the function calldispatch({})is made. This initializes thestatevalue with the default initial state value of thereducer.

Apart from a few details and edge cases, this is the full implementation of thecreateStore()method provided by thereduxpackage. As you can see, the technology behind the Reduxstoreis not particularly complicated, though the pattern it enforces is incredibly powerful.

Conclusion

By building a Redux store from scratch, you gain a deeper understanding of state management, actions, and subscriptions. This hands-on approach clarifies how Redux operates under the hood, reinforcing core concepts essential for efficient application development.

To explore Redux and state management further, consider taking Codecademy’sLearn Reduxcourse. It covers Redux fundamentals, including the store, actions, reducers, and middleware, helping you build scalable applications.

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

30% Free Preview Reached · Unlock Remaining 70%

Complete Payment to Unlock Full Guide & Source Code

You are viewing the 30% preview. Join TopCourseAI Pro to unlock the remaining 70% of this article, unblur all code snippets, and gain full access to all 412 technical projects and guides.

✓ Full Access to All 160+ Projects
✓ Instant Blur Removal Everywhere
✓ Production Source Code & Blueprints
✓ All Future Deep Dives Included
Instant Unhindered Activation ·
End of Preview · Unlock Complete Blueprint & Source Code

Ready to access the complete step-by-step tutorial?

Gain full unhindered access to all 412 technical projects and guides, unblur all source code snippets, and download runnable scripts.

Production AI Blueprints

Ready to build production-grade AI systems?

Enjoyed this guide? Continue exploring our library of 166+ production blueprints, multi-agent swarms, and autonomous agent systems with 100% unblurred, executable source code.