useSyncExternalStore is a React Hook that lets you subscribe to an external store.
First, let’s create a useOnlineStatus hook which automatically tracks whether the user’s device is online or offline.
useOnlineStatus
To use useSyncExternalStore and create useOnlineStatus, we need the following:
- A store
- A subscribe function that invokes a callback provided by React when the state inside the store changes
- A function that returns a snapshot of the current state from the store
Let’s create the store:
let currentNetworkStatus = {
online: navigator.onLine,
};
Let’s create the subscribe function:
The requirement for this function is that it accepts a callback, listens for state changes within the store, and returns a function that, when invoked, unsubscribes from listening to state changes.
(callback) => {
const update = () => {
const newStatus = { online: navigator.onLine };
if (newStatus.online !== currentNetworkStatus.online) {
currentNetworkStatus = newStatus;
callback();
}
};
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
}
Let’s create the snapshot function:
This function will just return the latest state from the store.
() => currentNetworkStatus.online
Let’s pack the subscribe fn and the snapshot fn into an object:
const network = {
getSnapshot: () => currentNetworkStatus,
subscribe: (callback) => {
const update = () => {
const newStatus = { online: navigator.onLine };
if (newStatus.online !== currentNetworkStatus.online) {
currentNetworkStatus = newStatus;
callback();
}
};
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
},
};
Now finally the useOnlineStatus:
const useOnlineStatus = () => {
const globalState = useSyncExternalStore(
(cb) => network.subscribe(cb),
() => network.getSnapshot()
);
return globalState
}
Next, let’s construct a usePageFocus hook that returns true if the page is focused and false if it is not.
usePageFocus
Following what we did for useOnlineStatus.
Let’s first create a store:
let focusState = {
focused: document.hasFocus(),
};
Let’s create the subscribe fn:
(callback) => {
const update = () => {
const newFocus = { focused: document.hasFocus() };
if (newFocus.focused !== focusState.focused) {
focusState = newFocus;
callback();
}
};
window.addEventListener("focus", update);
window.addEventListener("blur", update);
return () => {
window.removeEventListener("focus", update);
window.removeEventListener("blur", update);
};
}
Let’s create the snapshot fn:
() => focusState.focused
Now, bringing it all together, first by packing the snapshot fn and subscribe fn into an object and then creating the hook.
const pageFocus = {
getSnapshot: () => focusState,
subscribe: (callback) => {
const update = () => {
const newFocus = { focused: document.hasFocus() };
if (newFocus.focused !== focusState.focused) {
focusState = newFocus;
callback();
}
};
window.addEventListener("focus", update);
window.addEventListener("blur", update);
return () => {
window.removeEventListener("focus", update);
window.removeEventListener("blur", update);
};
},
};
Finally, the usePageFocus hook:
const usePageFocus = () => {
const globalState = useSyncExternalStore(
(cb) => pageFocus.subscribe(cb),
() => pageFocus.getSnapshot()
);
return globalState
}
Now, moving onto the mother of all hooks in this blog, the useGlobalState hook
useGlobalState hook
This hook works similar to the zustand api. For example:
we can create a hook to work with the loggedInUser state in a single file and then import it into any component without using providers or having us prop drill the state and state setter function into components.
Example implementation:
const useLoggedInUser = () => {
const [loggedInUser, setLoggedInUser] = useGlobalState("loggedInUser", {})
return [loggedInUser, setLoggedInUser]
}
So let’s now build the useGlobalState.
under the hood it is all useSyncExternalStore
Let’s continue the way we built usePageFocus and useOnlineStatus.
First the store:
We can implement the store like a simple Map.
const store = new Map();
Let’s implement the 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. We’ve looked at some example usages of useSyncExternalStore in React.