Skip to content

Instantly share code, notes, and snippets.

@developit
Created November 21, 2024 16:21
Show Gist options
  • Save developit/d635c2ff6159f6857facdd86331c5421 to your computer and use it in GitHub Desktop.
Save developit/d635c2ff6159f6857facdd86331c5421 to your computer and use it in GitHub Desktop.
import {
signal,
effect,
useSignal,
type useSignalEffect,
type Signal,
type ReadonlySignal,
} from '@preact/signals';
import { VNode, type ComponentChildren } from 'preact';
import {
useLayoutEffect,
useMemo,
useRef,
useId,
useEffect,
} from 'preact/hooks';
type Items<T> = T extends ReadonlyArray<infer U> ? U : never;
const Item = ({ v, k, f }: { v: any; k?: any; f: any }) => f(v, k);
/**
* Like signal.value.map(fn), but doesn't re-render.
*/
export function map<T extends ReadonlyArray<any> = any[]>(
signal: Signal<T>,
fn: (item: Items<T>, key: keyof T) => ComponentChildren,
) {
return <For each={signal} children={fn} />;
}
/**
* Like signal.value.map(fn), but doesn't re-render.
*/
export function For<T extends ReadonlyArray<any> = any[]>({
each,
children: f,
fallback,
}: {
each: Signal<T>;
children: (item: Items<T>, key: keyof T) => preact.ComponentChildren;
fallback?: ComponentChildren;
}) {
let c = useMemo(() => new Map(), []);
return (each.value?.map((v, k) => {
let item = c.get(v);
if (!item) c.set(v, (item = <Item v={v} k={k} f={f} />));
return item;
}) ?? fallback) as unknown as VNode;
}
/**
* Renders it's children when the given Signal is truthy.
* Note: children can also be a function that is passed the signal value.
*/
export function Show<T>({
when,
fallback,
children: f,
}: {
when: Signal<T>;
children: preact.ComponentChildren | ((value: T) => preact.ComponentChildren);
fallback?: preact.ComponentChildren;
}) {
const v = when.value;
return (
v ? typeof f === 'function' ? <Item v={v} f={f} /> : f : fallback
) as VNode;
}
/**
* useSignal, but re-rendering with a different value arg updates the signal.
* Useful for: const a = useLiveSignal(props.a)
*/
export const useLiveSignal: typeof useSignal = (value) => {
const s = useSignal(value);
if (s.peek() !== value) s.value = value;
return s;
};
/**
* useSignal, but works as a ref on DOM elements.
*/
export function useSignalRef<T>(value: T) {
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(this: Signal) {
return this.value;
},
set(this: Signal, v: any) {
this.value = v;
},
};
/**
* The `useLayoutEffect()` version of `useSignalEffect()`
*/
export const useSignalLayoutEffect: typeof useSignalEffect = (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