Skip to content

Instantly share code, notes, and snippets.

@LuckyArdhika
Created November 19, 2023 23:16
Show Gist options
  • Save LuckyArdhika/aed043321d32be122f1fa101b742627b to your computer and use it in GitHub Desktop.
Save LuckyArdhika/aed043321d32be122f1fa101b742627b to your computer and use it in GitHub Desktop.
Simple way for react 18 global state without context, export and import anywhere.
ref: https://stackoverflow.com/questions/63209420/react-global-state-no-context-or-redux
I have been using a similar approach and I really like it. I actually can't believe more people don't talk about this approach. I wrote a custom hook here React Superstore. It gives you the freedom to dispatch from anywhere in the app and shallow compares to avoid unwanted re-renders. I don't see any performance issues as long as you can avoid the unwanted re-renders.
In all it is a simple concept. You basically create a function to store your state and return 2 functions. One will be a function to set the stored state and one will be a hook to be used in the react component. In the hook you grab the setState function of react on initial render with a createEffect and store it in an array. You can then use this setState function to re render your component. So when you call the dispatch function you can just loop through these setState functions and call them.
Simple example:
import { useState, useEffect } from 'react'
const createStore = (initialStore) => {
let store = initialStore
const listeners = new Set()
const dispatch = (newStore) => {
// Make it like reacts setState so if you pass in a function you can get the store value first
store = typeof newStore === 'function' ? newStore(store) : newStore
listeners.forEach(listener => listener(() => store))
}
const useStore = () => {
const [, listener] = useState()
useEffect(() => {
listeners.add(listener)
return () => listeners.delete(listener)
}, [])
return store
}
return [useStore, dispatch]
}
Edit for React 18 using useSyncExternalStore:
import { useState, useEffect } from 'react'
const createStore = (initialStore) => {
let store = initialStore
const listeners = new Set()
const getStore = () => store
const dispatch = (newStore) => {
// Make it like reacts setState so if you pass in a function you can get the store value first
store = typeof newStore === 'function' ? newStore(store) : newStore
listeners.forEach(listener => listener(() => store))
}
const useStore = () => {
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
return useSyncExternalStore(subscribe, getStore)
}
return [useStore, dispatch]
}
Then just create a store and use in your component
const [useCount, setCount] = createStore(0)
const Display = () => {
const count = useCount()
return <div>{count}</div>
}
const addToCount = () =>
<button onClick={ () => setCount(count => count + 1}>+</button>
You can also just create a file with all of your stores in it and just export them from there and use them anywhere in your app. You can use them inside or outside of react components.
export const [useCount, setCount] = createStore(0)
Then if you want to have a complex store object and you want to avoid re renders you can do a shallow compare in the dispatch function to compare the store to the new store similar to what redux does. Something like the following:
const shouldUpdate = (a, b) => {
// (b) needs to be an object or you will have to do checks before looping make sure your store is an object or check it in this function
for( let key in b ) {
if(b[key] !== a[key]) return true
}
return false
}
and then in dispatch you can check this before firing the listener in your forEach loop.
const dispatch = (newStore) => {
const oldStore = store
store = typeof newStore === 'function' ? newStore(store) : newstore
if(!shouldUpdate(oldStore, store) return
listeners.forEach(listener => listener(() => store))
}
Its way less boilerplate than redux and seems to be much cleaner. The best thing is it allows you to decouple your actions from functions without attaching the actions to anything. You can simply create a store anywhere in your app and export the useStore and dispatch functions. Then you can dispatch from anywhere in your app.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment