Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Error’d: Pickup Sticklers

      September 27, 2025

      From Prompt To Partner: Designing Your Custom AI Assistant

      September 27, 2025

      Microsoft unveils reimagined Marketplace for cloud solutions, AI apps, and more

      September 27, 2025

      Design Dialects: Breaking the Rules, Not the System

      September 27, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Cailabs secures €57M to accelerate growth and industrial scale-up

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025
      Recent

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025

      Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

      September 28, 2025

      The first browser with JavaScript landed 30 years ago

      September 27, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured
      Recent
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Create Your Own Redux: Build a Custom State Management in React

    Create Your Own Redux: Build a Custom State Management in React

    July 11, 2025

    Managing state effectively is key to building scalable and maintainable frontend applications. While popular libraries like Redux, Zustand, Recoil and MobX offer powerful solutions, there are times when you want something custom – whether you’re trying to simplify logic or just understand how things really work under the hood.

    In this blog, I’ll walk you through how you can build your own state management system, called Pulse Store, or you can give any name to it. It’s a Redux-inspired, fully working solution built with plain JavaScript (ES6+).

    The goal here isn’t just to replace Redux – it’s to learn how such a tool functions behind the scenes. If you’ve ever wondered how state flows in Redux, how actions trigger updates, or how reducers are managed, then Pulse Store is a great way to explore all of that in a clean, understandable way.

    So, whether you’re working on a lightweight project that doesn’t need a heavy state library or you’re just curious about building things from scratch, this guide will give you the clarity and tools you need to implement your own custom solution with complete control.

    Prerequisites

    Before we start, make sure you have the following:

    • Node.js v16 or above
    • npm or yarn
    • Basic familiarity with JavaScript ES6, React, and Redux.
    • React/Next.js Application

    Create createPulseStore.js and createBlock.js files – The Heart of the System

    File 1: createPulseStore – The Store Manager

    First, create a file createPulseStore.js inside ‘/src/pulse/’ directory. This file is responsible for defining and managing the state and centralized store in the React/Next.js app.
    This is equivalent to the Redux store. It:

    • Combines all block states into a single state
    • Provides send() function to dispatch actions
    • Allow subscribe() to track state changes
    • Expose getState() to get updated state/read current state.

    Create a function in this file with the createPulseStore name and create two variables in it, one for states and the second for channels

    export function createPulseStore(blocks) {
        const state = {};
        const channels = [];
        blocks.map((item) => {
          state[item.name] = item.state;
        });
    }
    • State: This is the internal object that will hold the reference to the block’s state in your application.
    • Channels: This array holds listener functions (like components subscribed to state). Whenever any state updates, all listeners inside this array will be notified.
    • We will take blocks in the arguments to get the initial state while creating the store, and assign the initial value to the state with the help of a map.

    Next, we will create a function inside the createPulseStore function to expose the state value.

    export function createPulseStore(blocks) {
         …
         const getState = () => state;
    }
    

    Let’s create a new function to add all listener functions to the channels array and call the listener function immediately with the latest or initial state value.

    export function createPulseStore(blocks) {
         …
         const subscribe = (listener) => {
         channels.push(listener);
              listener(state);
         };
    }

    But there is an issue, when you add any listener to the channel array, it will always call even after your component was demounted from the DOM. To avoid this problem, we need to implement unsubscribe logic here. To achieve this, we just simply need to return a function that will remove your listener function from the channels array, and whenever we want to unsubscribe from the listener, we just simply need to call the returned function.

    export function createPulseStore(blocks) {
        …
        const subscribe = (listener) => {
        channels.push(listener);
        listener(state);
            return () => {
               const index = channels.indexOf(listener);
               channels.splice(index, 1);
            };
        };
    }

    Now let’s create the last function of the createPulseStore, which is a send function. The send function is similar to the dispatch function of Redux. This function will take a block instance as an argument, which state needs to be update,d and after the state update, it will call all listener functions through the channels array to notify with the updated state value of the store

    const send = async (block) => {
        state[block.name] = { ...block.state };
        channels.forEach((channel) => {
            channel(state);
        });
    };
    

    Lastly, we need to return all the inner functions from the createPulseStore function. And the implementation of create-store is complete with all functionalities. Below is the complete code of the ‘src/pulse/createPulseStore.js’ file

    export function createPulseStore(blocks) {
        const state = {};
        const channels = [];
    
        blocks.map((item) => {
            state[item.name] = item.state;
        });
    
        const getState = () => state;
    
        const subscribe = (listener) => {
            channels.push(listener);
                listener(state);
            return () => {
                const index = channels.indexOf(listener);
                channels.splice(index, 1);
            };
        };
    
        const send = async (block) => {
            state[block.name] = { ...block.state };
            channels.forEach((channel) => {
                channel(state);
            });
        };
    
        return {
            send,
            getState,
            subscribe,
        };
    }

    File 2: createBlock – Similar to a Reducer

    Create another file createBlock.js inside ‘/src/pulse/’ directory and create a function with name createBlock. This file function is responsible for defining individual “blocks” of the state. Each block has:

    • A unique name
    • An individual state object
    • Handler functions, which are the same as Redux’s action for updating the state.
    export function createBlock(name, initialState, handlerFunctions) {
        const state = initialState;
        let handlers = {};
    
        for (let i in handlerFunction) {
            handlers = {
                    ...handlers,
                    [i]: (payload) => {
                        handlerFunction [i](state, payload);
                        return { state,  name, handlers};
                    },
                };
        }
        return {
                state,
                name,
                handlers,
        };
    }

    In the createBlock function, we have created two variables for state and handler. Assign the initial state value to the state variable. There is also a for loop in this function that transforms your handler function with the block’s state value. When you call this handler function with only the payload, you can expect the block’s state to be included while creating the handler function. And lastly, return state, name, and handlers from this function as an object.

    This is pretty good for all we need for custom state management (custom-redux), and both files (createPulseStore and createBlock) are one-time processes. Now it’s time to use this custom redux (pulse state management) in a React/Next.js application and test it.

    We will create an application where logged-in user details will be stored and shared throughout the application.

    Create Your Blocks (reducer)

    Each block represents a slice of your application’s state, like a reducer in Redux. You must define the block name, initial state, and handler functions for updating the state, which will be passed as parameters in the createBlock function.

    const userBlock = createBlock(
        "user",
        { userInfo: {} },
        {
                updateUser: (state, payload) => {
                    state.userInfo = {
                        id: payload.id,
                        name: payload.name,
                    }
            },
                logout: async (state) => {
                    state.userInfo = {};
                },
        }
    );
    export const { updateUser, logout } = userBlock.handlers;

    In the above code, “user” is the block name, “{ userInfo: {} }” is the initial state value with an empty user info object and a handlers object that contains the updateUser and logout action functions. In the updateUser function, you can expect state and payload as parameters, where the state param is nothing but a global state of this block, and the payload will be anything like a string value, a number value, an array, or an object that was passed while calling this action/handler function. And the second handler (logout) will not be taking any payload and will remove the userInfo by replacing it with an empty object. After this, we simply need to export all handler functions by destructuring from blockName.handlers. Similarly, you can create multiple blocks with the help of the createBlock function.

    Create Store

    We can create our store instance using the createPulseStore function by passing all block instances in it.

    export const store = createPulseStore([userBlock])

    You can now import this store whenever and wherever it is needed. If you have multiple blocks, you need to pass them like this:

    export const store = createPulseStore([userBlock, productBlock, counterBlock, …,])

    And Boom, all custom redux implementation and setup is completed. Now it’s time to use it in the app.

    Using in React Components

    Here’s how you use this custom state management system in your React components.

    import { useEffect, useState } from "react";
    import { store, updateUser } from "./App";
    
    const Header = () => {
        const [userInfo, setUserDetails] = useState({});
    
            useEffect(() => {
                const unsubcribe = store.subscribe((state) => {
                        console.log("state=>", state.user)
                        setUserDetails(state.user.userInfo);
                });
                return unsubcribe;
         }, [])
    
            const handleLogin = () => {
            const userDetail = { id: 2, name: "Afnan Danish" }
            // this is static user info, we can fetch user info from any api
                store.send(updateUser(userDetail))
        }
     
            return (
                <header>
                        <h3>Logo</h3>
                    {
                            userInfo.id ?
                                    <h4>Welcome, {userInfo.name}</h4>
                            :
                                    <button onClick={handleLogin}>Login</button>
                        }
                </header>
            )
    }
    
    export default Header;

    In the above component, we’ve created a userInfo state using the useState hook and initialized with an empty object. We’ve added conditional rendering: when userInfo is available (i.e, has an id), the component displays a welcome message with the user’s name; otherwise, it shows a login button.

    And in the useEffect hook, the store.subscribe function sets up a listener that gets triggered (called) whenever the store’s state is updated. This callback receives the latest/updated state, and we can use it to update our local userInfo state using setUserInfo function.

    To update the global state in the store, we must call the handler (action) function with the help of store.send() function. As you can see from the handleLogin function, inside this function, we passed the userDetail object as a payload (param) to the updateUser handler, which we previously defined in the userBlock in the app.js file. Similarly, if you want to update any other state from a different block, you can import its corresponding handler and send (dispatch) it with store.send().

    If you want to remove or reset the user information from the store (i.e during logout), you simply need to dispatch the logout handler without any payload, as it’s already implemented in the userBlock.

    store.send(logout())

    Output

    Custom State Management

    Create a Hook to Updated State

    Let’s create custom react-hooks instead of subscribing in every component. Create a new file with the name ‘createPicker.js’ inside the ‘src/pulse/’ folder.

    import { useLayoutEffect, useState } from "react";
    export const createPicker = (store) => {
        return {
                usePick: (picker) => {
                    const [pick, setPick] = useState(picker(store.getState()));
                    
                    useLayoutEffect(() => {
                        const unSubcribe = store.subscribe((state) => {
                            setPick(picker(state));
                        });
                        return unSubcribe;
                    }, []);
    
                    return pick;
                },
        };
    };
    

    And you must create this usePick hook in the app.js file by passing the store instance. Below is the complete code of app.js file.

    App.js File

    import "./App.css";
    import Dashboard from "./Dashboard";
    import Header from "./Header";
    import SideBar from "./SiderBar";
    
    import { createPulseStore } from "./pulse/createPulseStore";
    import { createBlock } from "./pulse/createBlock";
    import { createPicker } from "./pulse/createPicker";
    
    const userBlock = createBlock(
        "user",
        { userInfo: {} },
        {
                updateUser: (state, value) => {
                    state.userInfo = {
                        id: value.id,
                        name: value.name,
                    }
                },
                logout: async (state) => {
                    state.userInfo = {};
                },
        }
    );
    
    export const { updateUser, logout } = userBlock.handlers;
    
    export const store = createPulseStore([ userBlock ]);
    
    export const { usePick } = createPicker(store);
    
    function App() {
        return (
                <>
                    <Header />
                    <main>
                        <SideBar />
                        <Dashboard />
                    </main>
                </>
        );
    }
    
    export default App;

    You can create Sidebar and Dashboard components too, with any content, or remove them from the app.js file.

    Updated Headers Component Code with Custom-Hook

    import { store, updateUser, usePick } from "./App";
    
    const Header = () => {
            const { userInfo } = usePick((state) => state.user);
    
            const handleLogin = () => {
                    const userDetail = { id: 2, name: "Afnan Danish" } 
                // this is static user info, we can fetch user info from any api too
                    store.send(updateUser(userDetail))
            }
     
            return (
                    <header>
                            <h3>Logo</h3>
                            {
                                userInfo.id ?
                                        <h4>Welcome, {userInfo.name}</h4>
                                :
                                        <button onClick={handleLogin}>Login</button>
                            }
                    </header>
            )
    }
    
    export default Header;

    In the above example, you need to pass a callback function in the usePick hook. You can expect the state in the function, which will give you all state values. You can return a block from the callback function. In the above example, we are returning the user block state by simply adding store.user and picking userInfo by destructuring it. You can use the below code, which will workthe  same as the above one:

    const userInfo = usePick((state) => state.user.userInfo);

    If you want to obtain all block states as you do in the store.subscribe function, you can get it like this:

    const data = usePick((state) => state); // you will get all blocks state object in the data

    Next Steps & Expansion Ideas

    The Pulse-Store you’ve built serves as a powerful foundation to understand how Redux-like state management works under the hood. But this is just the beginning. Here are some unique directions to explore and extend your store:

    • Async Flow Handling: Add support for asynchronous logic, such as API calls, and delays using Promise.
    • Custom Middleware: Create your own middleware pipeline to intercept and handle state updates – for logging, validation, event analytics, or side effects.
    • And Many More…

    Conclusion

    Building your own state management systems like Pulse Store not only helps you understand how tools like Redux, Zustand or MobX work under the hood but also gives you complete control over how state flows in your application. This approach might not replace production-ready libraries, but it’s a valuable exercise to deepen your grasp of core frontend architecture principle. This level of understanding is essential for debugging, scaling applications or even building your own micro-framework tailored to your app’s unique needs.

    Source: Read MoreÂ

    Facebook Twitter Reddit Email Copy Link
    Previous ArticlePerficient Nagpur Celebrates Contentstack Implementation Certification Success!
    Next Article Regolith – A JavaScript library immune to ReDoS attacks

    Related Posts

    Development

    Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

    September 28, 2025
    Development

    Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

    September 28, 2025

    1 Comment

    1. zoritoler imol on August 12, 2025 11:01 PM

      Thank you for every one of your effort on this blog. Kim loves participating in investigations and it is easy to see why. A lot of people learn all regarding the compelling means you give very important tricks by means of this web site and strongly encourage contribution from people on that point then our child is becoming educated a lot of things. Have fun with the rest of the year. You’re conducting a dazzling job.

      Reply
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Generative AI Development: Powering the Next Wave of Smart, Creative Applications✨

    Web Development

    This new Pixel 10 battery feature is stirring up controversy – here’s why

    News & Updates

    Mastering Mixed DML Operations in Apex

    Development

    Laravel Virtual Wallet

    Development

    Highlights

    Artificial Intelligence

    Radio Station Slammed for Pretending AI Host Is a Real Person

    April 25, 2025

    Sydney’s CADA radio station, part of the Australian Radio Network (ARN), has come under fire…

    Hands Off Protest Anti-Trump and Elon Musk Shirt

    April 6, 2025

    The Secret to Effective UX Workshops

    May 6, 2025

    Pakistan-Linked Hackers Expand Targets in India with CurlBack RAT and Spark RAT

    April 14, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.