React State Management Without Dependencies
How to create global state management in React applications without side dependencies and unnecessary rerendering
First of all, I would like to give special thanks to Jack Herrington for inspiring this topic.
React has made significant advances in the tools that help us create global state management. React has pushed us to a new level, and Redux’s huge boilerplate base and Mobx’s decorators have sunk into oblivion. Creating your own global state management without side dependencies is an easy challenge.
Today, we’ll learn about the useSyncExternalStore
hook and add polish to create state management without unnecessarily rendering our components.
Application Structure
Before starting the code, let’s check the structure of our test application. Each component will do iterations with the global store. We need that structure to know which component will be rerendered with the store’s manipulation.

Create Components
To simplify the development process, I’m gonna use https://vitejs.dev/, and choose React with JavaScript. Here’s the steps to do get started:
- Run
yarn create vite
- Choose your project name, framework:
React
, and language variant:javascript
- Add the following:
cd project_name
andyarn
- Remove
<React.StrictMode>
inmain.jsx
to avoid a second rerender
Now, let’s create components in src/components
. There should be four files with the following names: Header.jsx
, Body.jsx
, Shares.jsx
, and Footer.jsx
.
Create Store
In the src
folder, create the store
folder and put the following two files into it: initialState.js
and index.js
.
For initialState.js
, I put some nested objects and values that will get updated in our application. Here’s what the code looks like:
Before creating the function, let’s figure out how the store should work and what we should expect. Similar to Redux, we can use useState
hooks or useContexts
with useReducer
and apply them across the application. Let’s check the implementation with useState
, as shown below:
As you can see, we’re gonna reuse the useState
across the application. No magic; it’s a simple implementation that clarifies which component will be rerendered after manipulating the store. Let’s update our App.jsx
with store.useStore()
:
And let’s have Footer.jsx
access the current state with the following code:
- Run the app with
yarn dev
and hit the buttonUpdate Shares From App
with an open console.
You will see that all of our components are updated. In Footer
, we will read status
from the unmodified store, and this will always return active
.
But the problem is we didn’t update any values in Footer
because we got an updated object that rerendered the component. To avoid rerendering, we will create a selector and read the store from the hook useSelector
. Here’s how to do that:
const status = useSelector((state) => state.status);
The hook will use the function selector to get the current state from our store.
Update Components
Now, let’s create our remaining components. In each component, we’re gonna add console.log
to the name of that component. An alternative solution is to use Google Chrome with React Developer Tools. Here’s what that looks like:

Now, Footer.jsx
will use useSelector
from the created store.
For the Header
, we’re gonna only use setState
from store.
And for the Body
, we’re gonna use both functions to update and read the store.
And the last section, Shares
, will use the store to read data.
Finally, to wrap them all in one application, let’s put the components into App.jsx
. To check the App
component’s render as well, we’ll use the setState
function.
To highlight our components, let’s use App.css
to add borders.
header, footer, .body, .shares {
border: 1px solid #2c2c2c;
}
Store and Listeners
To prevent unnecessary redrawing, we need to create a useSelector
function. This will improve createStore
’s implementation with subscribe
. Subscribe
notifies React about store changes. Inside createStore.js
, let’s create the subscribe
function with listeners
.
With this technic, we can subscribe to our store and notify React about changes. As you can see, this function will also return listeners.delete
and allow us to unsubscribe. This technic came from the publisher-subscriber pattern which lets you subscribe and unsubscribe for changes. To receive notifications about changes, we must create another function, setState
.
The listener is always gonna get the current state and set it to listeners
.
And the last part, the createStore
function, uses the useSelector
hook and lets us get all the changes from our store.
But in this case, we are not gonna be able to get updated data because we are not subscribed to our changes from the state. To fix that, we have to apply the subscribe
function to the useSyncExternalStore
hook from React. This hook takes three arguments: subscribe
, getSnapshot
, and getServerSnapshot
to render on the server side.
The subscribe
function will register a callback to notify us about store changes. And combining () => selector(state)
and getSnapshot
will return our store’s current state. In this case, we won’t be using server-side rendering for a while.
Now, let’s run our server yarn dev
and check how the components will rerender. You will see something like this:

By clicking on the button, Update Shares From App
, the store’s data will update. This data is used only in Shares.jsx
, and that’s the only component that has to be rerendered because other components didn’t receive updates.
Now, click on Update Name And Age from Header
, and you will see that updates only happen in Body.jsx
. And if you click again, nothing is gonna rerender because the data is the same. This is absolutely fine.
What About Server-Side Rendering
To sync the server-side data and store, we need to improve the createStore
function. To test that, I suggest you create a Next JS
application and apply our created components to the index
view. While you’re at it, add the getServerSideProps
function to provide additional changes to the store’s data.
To apply new store data from our view, we have to initialize our store with server data from props
.
The init
function should get a new state and apply that to our current state. Here’s what that looks like:
The assignment will happen only once for the view
.
Conclusion
It’s fascinating! With one function, we solved the global state management problem without any boilerplate code or unnecessary rerendering. The hook useSyncExternalStore
helps us synchronize our store with our React application’s state. Just one function can connect our global store’s values across the entire application.
Resources
GitHub Repo: https://github.com/antonkalik/global-store
Want to Connect?
I'll be glad to keep in touch through Twitter.
It's always a pleasure to receive suggestions and comments
related to the topic. Feel free to ask any questions. Thank you!