Flux allows us to use global state in our applications, and interact with it using actions. Redux uses strings to denote actions, switch statements to parse the dispatch commands, and is synchronous.
Here is an implementation where we do not need to use strings, or switch, and which is async compatible.
The keys object has properties on it for each variable name in our store, and the actions object has a method for each action.
import {useStore, keys} from './useStore.js' | |
const ColoredTextBox = () => { | |
const [{text, color}, actions] = useStore([keys.text, keys.color]) | |
return( | |
<div | |
style={{backgroundColor:color}} | |
onClick={()=>actions.setColor(color=='red'?'green':'red')} | |
> | |
{text} | |
</div> | |
) | |
} |
We create our store in a separate file like this. The methods on the actions object use a shorthand notation here, each action must return the keys (or an array of keys) that it has mutated, so that the dispatch function knows which listeners to call.
We can also import the emit function to trigger state updates inside our async actions.
import { createStore, getKeys, emit } from './createStore.js' | |
const store = { | |
color: 'red', | |
text: 'default text', | |
todos: [ | |
'make an async store', | |
'publish medium article' | |
], | |
loading: false | |
} | |
export const keys = getKeys(store) | |
const actions = { | |
setColor(c){ | |
store.color = c | |
return(keys.color) | |
}, | |
setTextandColor(t,c){ | |
store.text = t | |
let ks = this.setColor(c) | |
return([keys.text, ks]) | |
}, | |
addTodo(t){ | |
store.todos.push(t) | |
return(keys.todos) | |
}, | |
async delayedSetColor(c){ | |
store.loading = true | |
emit(keys.loading) | |
await new Promise(resolve => setTimeout(resolve, 1000)) | |
store.color = c | |
store.loading = false | |
return([keys.color, keys.loading]) | |
} | |
} | |
export const useStore = createStore(store, actions) |
createStore.js looks like this. Let me know what you think in the comments. This implementation works well with async actions, using async/await notation in the action objects methods.
The makeKeys function means there are less bugs caused by misspelled strings.
import { useState, useEffect } from 'react' | |
var store = {} | |
var actions = {} | |
var listeners = {} | |
export const emit = keys => { | |
// for each key that needs its listeners called | |
flat(keys).forEach(key=>{ | |
// for each listener in that array | |
listeners[key].forEach(f=>{ | |
// copy the value to ensure react sees the update, and pass it to the listener | |
const val = store[key] | |
if(val instanceof Array){ | |
f([...val]) | |
} else if(val instanceof Object){ | |
f({...val}) | |
} else { | |
f(val) | |
} | |
}) | |
}) | |
} | |
export const createStore = ( newStore, newActions ) => { | |
store = newStore | |
Object.keys(newActions).forEach(action=>{ | |
// register each action, adding the emit function | |
const newAction = async (...args) =>{ | |
// get the list of store values that need updating, by calling the action (which returns the keys it needs updated) | |
// await the return value incase it is a promise | |
// then pass it to the emit function | |
const keys = await newActions[action](...args) | |
emit(keys) | |
} | |
actions[action] = newAction | |
}) | |
// initialize an empty array in the listeners object for each state variable | |
Object.keys(store).forEach(key=>listeners[key]=[]) | |
//build the useStore function | |
const useStore = keys => { | |
var state = {}; | |
flat(keys).forEach(key=>{ | |
// get the observable value, and the update function from the useState hook. | |
const [val,update] = useState(store[key]) | |
// when component mounts, register a listener with the update function, remove it when it unmounts. | |
useEffect( | |
()=>{ | |
listeners[key].push(update) | |
return(()=>listeners[key] = listeners[key].filter(l=>l != update)) | |
}, | |
[] | |
) | |
// place the observable value in the state object | |
state[key] = val | |
}) | |
return [state,actions] | |
} | |
return useStore | |
} | |
export const getKeys = store => { | |
const keys = {} | |
// return an object that has the state variables name as the key and the string as the value | |
Object.keys(store).forEach(key=>keys[key]=key) | |
return keys | |
} | |
// helper to convert a primitive, or an n-dimensional array, to a flat array | |
const flat = x => [].concat(x).reduce((memo, el) => { | |
var array = Array.isArray(el) ? flat(el) : [el]; | |
return memo.concat(array); | |
}, []); |
Top comments (0)