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

      This week in AI dev tools: Gemini API Batch Mode, Amazon SageMaker AI updates, and more (July 11, 2025)

      July 11, 2025

      JFrog finds MCP-related vulnerability, highlighting need for stronger focus on security in MCP ecosystem

      July 11, 2025

      8 Key Questions Every CEO Should Ask Before Hiring a Node.js Development Company in 2025

      July 11, 2025

      Vibe Loop: AI-native reliability engineering for the real world

      July 10, 2025

      One of Atlus’ best Xbox JRPGs that puts modern Final Fantasy games to shame is now on a 45% discount — This is your last chance to seize it as the Amazon Day Prime closes today

      July 11, 2025

      Don’t waste the LAST 24 hours of Amazon Prime Day sales buying a MacBook — buy this much better Windows laptop instead!

      July 11, 2025

      This fantastic Xbox remake of a classic Atlus JRPG we gave a perfect review score to is now 49% cheaper — Don’t miss the deadline for this Amazon Prime Day deal, which ends today

      July 11, 2025

      HP’s discount on one of the most powerful gaming laptops on the planet is absolutely UNBEATABLE — but you only have a few hours to get one!

      July 11, 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

      The details of TC39’s last meeting

      July 11, 2025
      Recent

      The details of TC39’s last meeting

      July 11, 2025

      Francisco Bergeret Paves the Way Through Strong Leadership at Perficient

      July 11, 2025

      Intelligent Automation in the Healthcare Sector with n8n, OpenAI, and Pinecone

      July 11, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      One of Atlus’ best Xbox JRPGs that puts modern Final Fantasy games to shame is now on a 45% discount — This is your last chance to seize it as the Amazon Day Prime closes today

      July 11, 2025
      Recent

      One of Atlus’ best Xbox JRPGs that puts modern Final Fantasy games to shame is now on a 45% discount — This is your last chance to seize it as the Amazon Day Prime closes today

      July 11, 2025

      Don’t waste the LAST 24 hours of Amazon Prime Day sales buying a MacBook — buy this much better Windows laptop instead!

      July 11, 2025

      This fantastic Xbox remake of a classic Atlus JRPG we gave a perfect review score to is now 49% cheaper — Don’t miss the deadline for this Amazon Prime Day deal, which ends today

      July 11, 2025
    • 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

    799 rejections… but he got the job! Braydon Coyer developer interview [Podcast #179]

    July 11, 2025
    Artificial Intelligence

    Introducing Gemma 3

    July 11, 2025
    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

    Perficient Named 2025 Gold Globee® Winner for Best Artificial Intelligence Service Provider

    Development

    Building interactive agents in video game worlds

    Artificial Intelligence

    CVE-2025-5014 – The Home Villas | Real Estate WordPress Theme File Deletion Vulnerability (Arbitrary File Deletion)

    Common Vulnerabilities and Exposures (CVEs)

    Red Hat Enterprise Linux (RHEL) quietly released an official image for WSL — but most of us won’t be able to use it

    News & Updates

    Highlights

    Tech & Work

    Smashing Animations Part 4: Optimising SVGs

    June 4, 2025

    SVG animations take me back to the Hanna-Barbera cartoons I watched as a kid. Shows…

    GraphCast: AI model for faster and more accurate global weather forecasting

    May 29, 2025

    CVE-2025-5617 – PHPGurukul Online Fire Reporting System SQL Injection Vulnerability

    June 4, 2025

    CVE-2025-47419 – Crestron Automate VX Insecure Communication Vulnerability

    May 6, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

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