Building a Reactive Store in React Using the Observer Design Pattern and Custom Hook
Ashok
Posted on August 17, 2024
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;
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;
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;
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;
import useStore from "./customhook";
const ChildComponentTwo = () => {
const count = useStore(state => state.count);
return (<>ChildComponentTwo = {count}</>)
}
export default ChildComponentTwo;
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!
Posted on August 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.