Skip to content

Instantly share code, notes, and snippets.

@cmlarsen
Last active July 6, 2020 20:22
Show Gist options
  • Save cmlarsen/a66f0030164d71b524565ef76a3bfc29 to your computer and use it in GitHub Desktop.
Save cmlarsen/a66f0030164d71b524565ef76a3bfc29 to your computer and use it in GitHub Desktop.
A bare bones shared state system using a pub/sub pattern and Reacts underlying useState hooks. See example in this Repl: https://repl.it/@cmlarsen/SharedState
import React, {useState, useEffect, useRef, useCallback, useMemo} from 'react'
import * as ReactDOM from 'react-dom'
import {get, set} from 'lodash'
//The guts of the pub/sub system
const sharedState = {
//Holds the state, this is mutable, but could easily be locked down
state: {},
//holds the watchers.
watchers:{},
//setup a new watcher for a "bit of state"
watch (bitOfState, callback) {
if (!this.watchers[bitOfState]) {
this.watchers[bitOfState] = new Set();
}
//add the callback to the set of watchers
this.watchers[bitOfState].add(callback)
//fire the initial value
callback(get(this.state, bitOfState))
},
//remote a watcher on a "bit of state"
unwatch (bitOfState, callback) {
this.watchers[bitOfState].delete(callback)
},
//set a value for a "bit of state"
set(bitOfState, value) {
//don't update if no change
if (get(this.state, bitOfState) === value) return
//update the state object
set(this.state, bitOfState, value)
//fan out the changes to all the watchers
for (let callback of this.watchers[bitOfState]) {
callback(get(this.state,bitOfState))
}
},
//Initialize the state
init(initialState){
this.state = initialState
}
}
//The main intercace hook
function useSharedState(bitOfState) {
const [value, setValue] = useState(get(sharedState.state,bitOfState));
useEffect(() => {
const watcher = _value => {
setValue(_value)
}
sharedState.watch(bitOfState, watcher)
return () => {
sharedState.unwatch(bitOfState, watcher)
}
}, [])
const setter = useCallback((input) => {
let value;
if(typeof input === 'function'){
value = input(get(this.state,bitOfState))
} else {
value = input
}
sharedState.set(bitOfState, input)
},[])
return [value, setter]
}
function ExampleComponent1(){
const [time, setTime] = useSharedState('time')
const [counter, setCounter] = useSharedState('nested.kinda.deep')
const [weather, setWeather] = useSharedState('weather')
useEffect(()=>{
setInterval(()=>{
//setting state based on current value
setCounter(_value=>_value+1)
},1000)
},[])
useEffect(()=>{
fetch('https://api.weather.gov/gridpoints/TOP/31,80/forecast').then(res=>res.json()).then(result=>{
setWeather(result.properties.periods[0].shortForecast)
})
},[]);
return <div>Component 1 <br/> {counter} <br/> { time.toISOString()} <br/> {weather} </div>
}
function ExampleComponent2(){
const [time, setTime] = useSharedState('time')
const [counter] = useSharedState('nested.kinda.deep')
const [weather] = useSharedState('weather')
useEffect(()=>{
setInterval(()=>{
setTime(new Date())
},1000)
},[])
return <div>Component 2 <br/> {counter} <br/> { time.toISOString()} <br/> {weather} </div>
}
function App(){
sharedState.init({
time:new Date(),
weather:undefined,
nested:{
kinda:{
deep:0
}
}})
return <div className="App">
<ExampleComponent1/>
<ExampleComponent2/>
</div>
}
ReactDOM.render(<App />, document.getElementById('root'));
@cmlarsen
Copy link
Author

cmlarsen commented Jul 6, 2020

Could have problems if you subscribe to a child and then update it's parent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment