@silyze/react-loadable
v1.0.0
Published
An object that represents a loadable value
Readme
React Loadable
A small utility library for representing and consuming asynchronous values in React applications using the new use hook and server-rendering APIs. It enables you to wrap promises or values into a consistent "loadable" type, extract state for rendering logic, and seamlessly suspend or read values in both client and server environments.
Features
loadable: Wrap a promise, value, or undefined into aLoadable<T>.extractState: Inspect the current state of aLoadable<T>(pending, loading, or ready).useLoadable: A React hook that leveragesusefor suspense or optional immediate reads.synchronize: Read a readyLoadable<T>synchronously or apply a mapping function when it’s available.
Installation
Install from npm:
npm install @silyze/react-loadableQuickstart
import { loadable, useLoadable } from "@silyze/react-loadable";
import React, { useSyncExternalStore } from "react";
// Wrap an async fetch call into a Loadable
function useData<T>(url: string) {
function fetcher() {
return fetch(url).then((res) => res.json() as Promise<T>);
}
return loadable(useSyncExternalStore(subscribe, fetcher, fetcher));
}
// In a server-rendered or suspense-enabled component
function MyComponent() {
const data = useLoadable(useData<{ message: string }>("/api/msg"));
if (!data) {
return <div>Loading…</div>;
}
return <div>{data.message}</div>;
}API Reference
LoadingSymbol
A unique Symbol used as the key to store a pending promise inside a loadable object.
const LoadingSymbol: unique symbol;
export type LoadingSymbol = typeof LoadingSymbol;Loadable<T>
Represents a value that may be loading. Either a direct T, or an object containing a promise under the LoadingSymbol key, or null for pending.
export type Loadable<T> = T | { [LoadingSymbol]: Promise<T> | null };loadable<T>(promiseOrT?: Promise<T> | T): Loadable<T>
Wraps a promise, value, or undefined into a Loadable<T>:
- If given a
Promise<T>, returns{ [LoadingSymbol]: promise }. - If given
undefined, returns{ [LoadingSymbol]: null }(pending). - Otherwise, returns the raw value
T.
function loadable<T>(promiseOrT: Promise<T> | T | undefined): Loadable<T>;LoadableState<T>
An enumeration of loadable states:
{ status: "pending" }— promise is not yet created.{ status: "loading" }— promise is active (not resolved).{ status: "ok"; value: T }— value is ready.
export type LoadableState<T> =
| { status: "pending" }
| { status: "loading" }
| { status: "ok"; value: T };extractState<T>(loadable: Loadable<T>): LoadableState<T>
Inspect a Loadable<T> and return its state:
function extractState<T>(loadable: Loadable<T>): LoadableState<T>;Example:
const state = extractState(loadable(Promise.resolve(42)));
// → { status: "loading" }useLoadable<T>(loadable: Loadable<T>, wait?: boolean): T | undefined
A React hook for consuming loadables:
- If the loadable is ready, returns the value.
- If pending (
[LoadingSymbol]: null), returnsundefined. - If loading and
waitistrue(default), suspends viause(promise). - If loading and
waitisfalse, returnsundefinedimmediately.
function useLoadable<T>(loadable: Loadable<T>, wait?: boolean): T | undefined;synchronize<T, R?>(loadable: Loadable<T>, onValue?: (value: T) => R): T | R | undefined
Synchronously read a Loadable<T> without React hooks:
- If not loaded, returns
undefined. - If loaded and
onValueis omitted, returns the rawT. - If loaded and
onValueis provided, returns the mappedR.
function synchronize<T>(loadable: Loadable<T>): T | undefined;
function synchronize<T, R>(
loadable: Loadable<T>,
onValue: (value: T) => R
): R | undefined;Examples
Extracting State
import { loadable, extractState } from "@silyze/react-loadable";
const l1 = loadable(Promise.resolve(100));
console.log(extractState(l1)); // { status: "loading" }
const l2 = loadable(42);
console.log(extractState(l2)); // { status: "ok", value: 42 }Controlled Rendering with extractState
import React from "react";
import { loadable, extractState } from "@silyze/react-loadable";
function StatusDisplay(loadableValue) {
const state = extractState(loadableValue);
switch (state.status) {
case "pending":
return <div>Pending…</div>;
case "loading":
return <div>Loading…</div>;
case "ok":
return <div>Value: {state.value}</div>;
}
}