Skip to content

Instantly share code, notes, and snippets.

@tokland
Last active March 1, 2019 21:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tokland/6ae023f89d0a03f7aa38632f3b13b340 to your computer and use it in GitHub Desktop.
Save tokland/6ae023f89d0a03f7aa38632f3b13b340 to your computer and use it in GitHub Desktop.
Stateful component wrapper over wavesoft/dot-dom with a functional reducer (hooks supported)
/* Stateful component wrapper over wavesoft/dot-dom with a functional reducer (hooks supported) */
function mapValues(input, mapper) {
return Object.keys(input).reduce((acc, key) => {
const value = mapper(input[key], key);
return value ? Object.assign(acc, { [key]: value }) : acc;
}, {});
}
function _setState(setState, newState$) {
if (newState$) {
Promise.resolve(newState$).then(newState => setState(newState));
}
}
function statefulComponent({ view, initialState, actions }) {
return (props, state_, setState, hooks) => {
const state = Object.keys(state_).length === 0 ? initialState(props) : state_;
const actionHandlers = mapValues(actions, (action, key) => {
switch (key) {
case "onMount":
hooks.m.push(domEl => _setState(setState, action(state, props, domEl)));
return null;
case "onUnmount":
hooks.u.push(() => _setState(setState, action(state, props)));
return null;
case "onUpdate":
hooks.d.push((domEl, prevDomEl) =>
_setState(action(setState, state, props, domEl, prevDomEl))
);
return null;
default:
return (...eventArgs) => _setState(setState, action(state, props, eventArgs));
}
});
return view(props, state, actionHandlers);
};
}
/* Example: a Counter */
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const Counter = statefulComponent({
initialState: props => ({
clicks: props.initialValue || 0,
}),
actions: {
// How to use dot-dom lifecycle events: mount, unmount, update
onMount: (state, props, domElement) => console.log("mounted", { state, props, domElement }),
onUpdate: (state, props, domElement, previousDomElement) =>
console.log("update", { state, props, domElement, previousDomElement }),
onUnmount: (state, props) => console.log("unmounted", { state, props }),
// Synchronous action, return the new state
increment: state => ({ clicks: state.clicks + 1 }),
// Asynchronous action, return a promise that resolves to the new state
incrementWithDelay: state => wait(1000).then(() => ({ clicks: state.clicks + 1 })),
},
view: (_props, state, handlers) => {
return H("div", [
H("span", `Value: ${state.clicks}`),
H("button", { onclick: handlers.increment }, `Increment`),
H("button", { onclick: handlers.incrementWithDelay }, `Increment with 1s delay`),
]);
},
});
function main() {
R(H(Counter, { initialValue: 1 }), document.body);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment