Created
March 4, 2023 15:02
-
-
Save developit/a72311c247756f24da5b22d19c9dad48 to your computer and use it in GitHub Desktop.
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 { 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