A simple tutorial about creating a useGlobalState hook in React

A simple tutorial about creating a useGlobalState hook in React

posted 2 min read

Hi Guys,

SO here is what we are going to learn to code today:

const useLoggedInUser = () => {
const [loggedInUser, setLoggedInUser] = useGlobalState("loggedInUser", {})

return [loggedInUser, setLoggedInUser]

}

We can export this function and use the hook to access the loggedInUser state in any component, without needing to prop drill or use providers. While there's nothing wrong with prop drilling or using providers, this is another valid approach in React for managing global state without relying on third-party libraries like Zustand.

So let's go:

To create useGlobalState we are going to rely on the useSyncExternalStore hook in React.

Here is how React defines the usage of the useSyncExternalStore hook:

useSyncExternalStore is a React Hook that lets you subscribe to an external store.

And

useSyncExternalStore returns the snapshot of the data in the store. You need to pass two functions as arguments:
The subscribe function should subscribe to the store and return a function that unsubscribes.
The getSnapshot function should read a snapshot of the data from the store.

So basically to work with this hook we need a store, a subscribe function and a getSnapShot function.

Let's first create a simple store:

const store = new Map();

This store keeps track of the states and the listeners which we will attach to it via useSyncExternalStore.

Let's now create a subscribe function:

const subscribeToStore = (key, listener) => {
const entry = store.get(key);
if (!entry) return () => { };
entry.listeners.add(listener);
return () => entry.listeners.delete(listener);

}

This function allows components to subscribe to changes in specific pieces of global state and get notified when that state changes.

The second param in the above function will be directly passed by React when specific pieces of global state changes, all thanks to useSyncExternalStore magic.

Now let’s implement the getSnapShot function:

const getSnapshotFromStore = (key) => {
const entry = store.get(key)
return entry?.value

}

Now let’s put all these pieces together to create the magic hook useGlobalState:

const useGlobalState = (key, initialValue) => {

initializeStateInStore(key, initialValue);

const globalState = useSyncExternalStore(
    (cb) => subscribeToStore(key, cb),
    () => getSnapshotFromStore(key)
);

return [globalState, (newValue) => setStateInStore(key, newValue)]

}

Notice that there are two function in the useGlobalState that we have not yet seen in this blog: initializeStateInStore, setStateInStore

initializeStateInStore is used to add an entry into the map as and when we initialize a new state and it is implemented like so:

const initializeStateInStore = (key, initial) => {
if (!store.has(key)) {
    store.set(key, {
        value: initial,
        listeners: new Set(),
    })
}

}

setStateInStore is a function that changes values in the global store and notifies all subscribers and it is implemented like so:

const setStateInStore = (key, newValue) => {
const entry = store.get(key)
if (entry) {
    entry.value = newValue;
    entry.listeners.forEach((fn) => fn());
}

}

So there we have it the useGlobalState function.

So that’s it for today, guys.

Here is the link to my github repo where I have implemented this function:

useGlobalState

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Nice breakdown! Really appreciate the clear step-by-step approach to building a global state hook without extra libraries. Quick question—how does this pattern hold up in larger apps with lots of state updates? Curious if you’ve faced any performance challenges.

Great tutorial! I love seeing how useSyncExternalStore can be used to build custom state management solutions. Your approach with the Map-based store and subscription pattern is really clean and educational.

This actually reminds me a lot of the core principles behind Nucleux - we took a similar approach but extended it with atomic updates, organized class-based stores, and additional features like persistence and dependency injection. The beauty of useSyncExternalStore is that it enables these kinds of lightweight, performant state solutions without the complexity of larger libraries.

Your implementation would be perfect for simpler use cases, while Nucleux might be useful if you need more organization as the state grows. Would love to see what you think of the approach - and if you're interested in contributing to open source state management tools, we're always looking for contributors who understand these patterns as well as you do!

Thanks for the clear breakdown of how the pieces fit together. This kind of content really helps developers understand what's happening under the hood.

Thank you for reading my post. I don't know about performance challenges but if the global state has to be persisted then we must add extra logic to handle that.

Otherwise,

let's take a state like isAttemptToOrder,

for my requirement I just stored this in the redux and did state changes using redux helpers.

But now I can simply use this hook and work with it at any level in the component tree without going to redux or zustand.

I am not saying that this has any advantage over the other.

All I am saying is that this is an other approach to work with global states that the latest React versions has exposed to us.

It depends upon our use cases and the type of project we are in to decide wether we must use it or not!

Really well detailed tutorial

More Posts

Clean React code pattern with the useImperativeHandle hook

Tony Edgal - May 20

From Props to Context: Simplifying State Management in React

Aneesa Fatima - Jul 7, 2024

React + TypeScript — The Practical Refresher

Sibasish Mohanty - Sep 9

What is React? A Complete Beginner's Guide

Abdelhakim Baalla - Jul 29

Starting a new web app with a React project, should you go with React JavaScript or TypeScript?

Sunny - Jun 24
chevron_left