npm package: @silyze/react-loadable
Description: An object that represents a loadable value
Author: Mihail Mojsoski
Latest version: 1.0.0

# 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 a `Loadable<T>`.
- **`extractState`**: Inspect the current state of a `Loadable<T>` (pending, loading, or ready).
- **`useLoadable`**: A React hook that leverages `use` for suspense or optional immediate reads.
- **`synchronize`**: Read a ready `Loadable<T>` synchronously or apply a mapping function when it’s available.

---

## Installation

Install from npm:

```bash
npm install @silyze/react-loadable
```

## Quickstart

```ts
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.

```ts
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.

```ts
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`.

```ts
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.

```ts
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:

```ts
function extractState<T>(loadable: Loadable<T>): LoadableState<T>;
```

**Example**:

```ts
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`), returns `undefined`.
- If loading and `wait` is `true` (default), suspends via `use(promise)`.
- If loading and `wait` is `false`, returns `undefined` immediately.

```ts
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 `onValue` is omitted, returns the raw `T`.
- If loaded and `onValue` is provided, returns the mapped `R`.

```ts
function synchronize<T>(loadable: Loadable<T>): T | undefined;
function synchronize<T, R>(
  loadable: Loadable<T>,
  onValue: (value: T) => R
): R | undefined;
```

---

## Examples

### Extracting State

```ts
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`

```tsx
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>;
  }
}
```