DEV Community

Cover image for Building a Reactive Store in React Using the Observer Design Pattern and Custom Hook
Ashok
Ashok

Posted on

Building a Reactive Store in React Using the Observer Design Pattern and Custom Hook

In modern React development, managing state across components can be challenging, especially when you want to keep different components in sync with shared state. One effective solution to this problem is using the Observer Design Pattern combined with a custom hook in React. In this article, we'll walk through how to implement a simple reactive store using the Observer pattern and leverage it in a React application.

Step 1: Implementing the ObserverPattern Class

The Observer Pattern is a behavioral design pattern where an object (known as the subject) maintains a list of its dependents (observers) and notifies them of any state changes. Here’s how we can implement it in JavaScript:

class ObserverPattern {
    constructor(initialState = {}) {
        this.state = initialState;
        this.observers = [];
    }

    subscribe(event) {
        this.observers.push(event);
    }

    notify() {
        this.observers.forEach(observer => observer(this.state));
    }

    setState(newState) {
        this.state = { ...this.state, ...newState };
        this.notify();
    }

    getState() {
        return this.state;
    }

    unsubscribe(event) {
        this.observers = this.observers.filter(observer => observer !== event);
    }
}

const Store = new ObserverPattern({ count: 10 });

export default Store;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • State Management: The ObserverPattern class holds the state in the state object and provides methods to update (setState) and access (getState) this state.
  • Observer List: The observers array stores the functions that should be called whenever the state changes.
  • Notify Method: The notify method iterates over all observers and triggers them with the current state, ensuring that any subscribed component gets updated.
  • Unsubscribe Method: This method removes a specific observer from the list to avoid memory leaks.

Step 2: Creating a Custom Hook for React Components

The next step is to create a custom hook, useStore, that allows React components to interact with our ObserverPattern store.

import { useEffect, useState } from "react";
import Store from "./customhook";

const useStore = (selector) => {
    const [state, setState] = useState(selector(Store.getState()));

    useEffect(() => {
        const event = (newState) => {
            setState(selector(newState));
        };

        Store.subscribe(event);

        return () => {
            Store.unsubscribe(event);
        };
    }, [selector]);

    return state;
};

export default useStore;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Selector Function: The useStore hook accepts a selector function to pick specific parts of the state. This makes it flexible and efficient by only updating when the selected state changes.
  • State Synchronization: The hook initializes local state with the selected part of the store’s state. When the store’s state changes, the hook updates the component’s state, triggering a re-render.
  • Effect Hook: The useEffect hook handles subscribing to the store on mount and unsubscribing on unmount, ensuring that the component stays in sync with the store while avoiding memory leaks.

Step 3: Using the Store in React Components

Now, let's see how we can use this custom hook within React components to manage and display the state.

Parent Component:
The Parent component provides a button to increment the count state in the store.

import Store from "./customhook";

const Parent = () => {
    let count = Store.getState().count;
    const increase = () => {
        ++count;
        Store.setState({ count });
    }

    return (
        <>
            <button onClick={increase}>Increase</button>
        </>
    );
}

export default Parent;

Enter fullscreen mode Exit fullscreen mode

Child Components:
Both ChildComponentOne and ChildComponentTwo subscribe to the count state using the useStore hook.

import useStore from "./customhook"; // custom hook

const ChildComponentOne = () => {
    const count = useStore(state => state.count);

    return (<>ChildComponentOne = {count}</>)
}

export default ChildComponentOne;

Enter fullscreen mode Exit fullscreen mode
import useStore from "./customhook";

const ChildComponentTwo = () => {
    const count = useStore(state => state.count);

    return (<>ChildComponentTwo = {count}</>)
}

export default ChildComponentTwo;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Parent Component: The Parent component manages the increase function, which increments the count and updates the store. Both child components are rendered below the button.
  • Child Components: These components are connected to the store via the useStore hook. Whenever the count state in the store changes, they automatically re-render to reflect the new value.

Conclusion

By leveraging the Observer Design Pattern and custom hooks, you can create a powerful and flexible state management solution in React. This approach allows you to easily keep multiple components in sync without relying on more complex state management libraries. The separation of concerns between state management and UI components also makes the code more maintainable and reusable.

Feel free to adapt this pattern to suit your project’s needs, and enjoy the reactive power of your new custom store in React!

Top comments (0)