Building a Redux Store from Scratch Using Vanilla JavaScript
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.'
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.
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.
Building a Neural Network using PyTorch
Next BlueprintBuilding an AI Chatbot with Rasa and Ollama
Related Blueprints & Guides
View all 166 →Accessibility on the Platform
Codecademy is committed to providing a high-quality learning experience for all learners. That includes accommodating different usage forms such as auditory, motor, or visual restrictions, limited hardware, or other acce...
Web developmentAffordances, Signifiers, and Clickability
For users on the web, the mouse click is perhaps the most fundamental human-computer interaction. The webbecamethe web partially through the power ofhypertext, or text in one document that links to other documents or res...
Web developmentAuthentication and OAuth
Authenticationis the process used by applications to determine and confirm identities of users. It ensures that the correct content is shown to users. More importantly, it ensures that incorrect content is secured and u...
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.