Skip to content

Instantly share code, notes, and snippets.

@p7g
Last active January 30, 2021 18:01
Show Gist options
  • Save p7g/bcf6047ee5408178c35f1c52080a608e to your computer and use it in GitHub Desktop.
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)
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