Skip to content

Instantly share code, notes, and snippets.

@lynlevenick
Created April 28, 2020 22:14
Show Gist options
  • Save lynlevenick/e6e8a543f1fdae699cab2a12b0a5444b to your computer and use it in GitHub Desktop.
Save lynlevenick/e6e8a543f1fdae699cab2a12b0a5444b to your computer and use it in GitHub Desktop.
Implementation of `useEffect` and a basic metronome in Crank.js
/* @jsx createElement */
import { createElement } from "@bikeshaving/crank/cjs";
type EffectCleanup = undefined | (() => void);
function* useEffectInternal<F extends (...args: any[]) => EffectCleanup>(
effect: F
): Generator<void, void, Parameters<F>> {
let dependencies = yield;
let cleanup: EffectCleanup = effect(...dependencies);
try {
while (true) {
const newDependencies = yield;
if (
newDependencies.some((elt, idx) => !Object.is(elt, dependencies[idx]))
) {
// Some dependency differed, clean up and call the effect again
// to unmount/remount
if (cleanup != null) {
cleanup();
}
dependencies = newDependencies;
cleanup = effect(...dependencies);
}
}
} finally {
if (cleanup != null) {
cleanup();
}
}
}
function useEffect<F extends (...args: any[]) => EffectCleanup>(
effect: F,
dependencies: Parameters<F>
) {
const generator = useEffectInternal(effect);
generator.next(dependencies);
return generator;
}
function useInterval(cb: () => void, delay?: number) {
return useEffect(
// dependencies are passed in to the function, since it can't change
(delay?: number) => {
if (delay != null) {
const interval = setInterval(cb, delay);
return () => clearInterval(interval);
}
},
[delay]
);
}
function* Metronome({ bpm }) {
let ticktock = false;
const timer = useInterval(() => {
ticktock = !ticktock;
this.refresh();
});
try {
for ({ bpm } of this) {
// changed to array to support generic useEffect impl
timer.next([60000 / bpm]);
yield (
<div>
Metronome: {bpm} bpm => {ticktock ? "Tick" : "Tock"}
</div>
);
}
} finally {
// explicit cleanup to ensure interval is cleared
timer.return();
}
}
function* Application() {
let visible = true;
let bpm = 60;
while (true) {
yield (
<div>
<button
onclick={() => {
visible = !visible;
this.refresh();
}}
>
Toggle visibility
</button>
{visible ? <Metronome bpm={bpm} /> : undefined}
<input
type="range"
min="30"
max="480"
defaultValue={bpm}
oninput={(e) => {
bpm = +e.target.value;
this.refresh();
}}
/>
</div>
);
}
}
export default Application;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment