Last active
January 30, 2021 18:01
-
-
Save p7g/bcf6047ee5408178c35f1c52080a608e to your computer and use it in GitHub Desktop.
A small, crappy version of facebookexperimental/Recoil (demo: https://codesandbox.io/s/mini-recoil-yqipr)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const _Root = React.createContext(); | |
export function Root({ children }) { | |
const [state] = React.useState(() => new Map()); | |
return <_Root.Provider value={state}>{children}</_Root.Provider>; | |
} | |
function makeNodeState(node) { | |
const state = { | |
node, | |
value: undefined, | |
version: 0, | |
dependents: new Set(), | |
listeners: new Set() | |
}; | |
if (node._type === "selector") { | |
state.dependencies = new Set(); | |
} | |
return state; | |
} | |
function getState(root, node) { | |
let state = root.get(node.key); | |
if (!state) { | |
state = makeNodeState(node); | |
root.set(node.key, state); | |
} | |
return state; | |
} | |
function getset(root, node, state, alreadyComputing = undefined) { | |
return { | |
get(depNode) { | |
state.dependencies.add(depNode.key); | |
const value = computeValue(root, depNode, alreadyComputing); | |
root.get(depNode.key).dependents.add(node.key); | |
return value; | |
}, | |
set(depNode, value) { | |
setValue(root, depNode, value); | |
} | |
}; | |
} | |
function notifyDependents(root, node, alreadyComputing = undefined) { | |
const state = getState(root, node); | |
state.listeners.forEach((l) => l(state.value)); | |
state.dependents.forEach( | |
(dep) => | |
alreadyComputing?.has(dep) || | |
computeValue(root, root.get(dep).node, alreadyComputing) | |
); | |
} | |
function computeValue(root, node, alreadyComputing = new Set()) { | |
let state = getState(root, node); | |
alreadyComputing = new Set(alreadyComputing); | |
alreadyComputing.add(node.key); | |
if (state.version !== 0 && node._type !== "atom") { | |
let valid = true; | |
state.dependencies.forEach((dep) => { | |
const depState = root.get(dep); | |
if (!depState || depState.version > state.version) { | |
valid = false; | |
} | |
}); | |
if (valid) { | |
return state.value; | |
} | |
} | |
if (node._type === "atom" && state.version === 0) { | |
state.value = node.default; | |
} else if (node._type === "selector") { | |
state.value = node.get(getset(root, node, state, alreadyComputing)); | |
} | |
state.version += 1; | |
notifyDependents(root, node, alreadyComputing); | |
return state.value; | |
} | |
function setValue(root, node, setter) { | |
const state = getState(root, node); | |
let newValue; | |
if (typeof setter === "function") { | |
newValue = setter(state.value); | |
} else { | |
newValue = setter; | |
} | |
if (node._type === "atom") { | |
state.value = newValue; | |
} else { | |
node.set(getset(root, node, state)); | |
} | |
state.version += 1; | |
notifyDependents(root, node); | |
} | |
function listen(root, node, listener) { | |
const state = getState(root, node); | |
state.listeners.add(listener); | |
return () => state.listeners.delete(listener); | |
} | |
export function atom({ key, default: default_ }) { | |
return { _type: "atom", key, default: default_ }; | |
} | |
export function selector({ key, get, set }) { | |
return { _type: "selector", key, get, set }; | |
} | |
export function useStateValue(node) { | |
const root = React.useContext(_Root); | |
const [state, _setState] = React.useState(() => computeValue(root, node)); | |
React.useEffect(() => { | |
return listen(root, node, (value) => { | |
_setState(value); | |
}); | |
}, [root, node, _setState]); | |
return state; | |
} | |
export function useState(node) { | |
const root = React.useContext(_Root); | |
const state = useStateValue(node); | |
const setState = React.useCallback( | |
(setter) => { | |
if (node._type === "selector" && !node.set) { | |
throw new Error(`${node.key} is read-only`); | |
} | |
setValue(root, node, setter); | |
}, | |
[root, node] | |
); | |
return [state, setState]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment