Skip to content

Instantly share code, notes, and snippets.

@developit
Created March 4, 2023 15:02
Show Gist options
  • Save developit/a72311c247756f24da5b22d19c9dad48 to your computer and use it in GitHub Desktop.
Save developit/a72311c247756f24da5b22d19c9dad48 to your computer and use it in GitHub Desktop.
import { useSignal, signal, effect } from '@preact/signals';
import { useLayoutEffect, useMemo, useRef } from 'preact/hooks';
/** @template T @typedef {T extends (infer U)[] ? U : never} Items */
/** @param {{ v, k?, f }} props */
const Item = ({ v, k, f }) => f(v, k);
/**
* Like signal.value.map(fn), but doesn't re-render.
* @template {any[]} T
* @param {Signal<T>} signal
* @param {(item: Items<T>) => import('preact').ComponentChildren} fn
*/
export function map(signal, fn) {
return <For each={signal} children={fn} />;
}
/**
* Like signal.value.map(fn), but doesn't re-render.
* @template {any[]} T
* @param {{ each: Signal<T>, children: (item: Items<T>) => preact.ComponentChildren, fallback?: preact.ComponentChildren }} props
* @returns {any} - our types are too strict and don't allow arrays
*/
export function For({ each, children: f, fallback }) {
let c = useMemo(() => new Map(), []);
return (
each.value?.map(
(v, k, x) => c.get(v) || (c.set(v, (x = <Item {...{ key: v, v, k, f }} />)), x)
) ?? fallback
);
}
/**
* Renders it's children when the given Signal is truthy.
* Note: children can also be a function that is passed the signal value.
* @template T
* @param {{ when: Signal<T>, children: preact.ComponentChildren | ((value: T) => preact.ComponentChildren), fallback?: preact.ComponentChildren }} props
* @returns {any} - our types are too strict and don't allow arrays
*/
export function Show({ when, fallback, children: f }) {
const v = when.value;
return v ? typeof f === 'function' ? <Item {...{ v, f }} /> : f : fallback;
}
/**
* useSignal, but re-rendering with a different value arg updates the signal.
* Useful for: const a = useLiveSignal(props.a)
* @type {typeof useSignal}
*/
export function useLiveSignal(value) {
const s = useSignal(value);
if (s.peek() !== value) s.value = value;
return s;
}
/**
* useSignal, but works as a ref on DOM elements.
* @template T
* @param {T} value
*/
export function useSignalRef(value) {
const ref = /** @type {Signal<T> & { current: T }} */ (useSignal(value));
if (!('current' in ref)) Object.defineProperty(ref, 'current', refSignalProto);
return ref;
}
const refSignalProto = {
configurable: true,
get() {
return this.value;
},
set(v) {
this.value = v;
},
};
/**
* The `useLayoutEffect()` version of `useSignalEffect()`
* @type {typeof import('@preact/signals').useSignalEffect}
*/
export function useSignalLayoutEffect(cb) {
const callback = useRef(cb);
callback.current = cb;
useLayoutEffect(() => effect(() => callback.current()), []);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment