npm package: @liberfi.io/hooks
Description: A collection of framework-agnostic React utility hooks used across the LiberFi frontend. Provides common patterns — boolean state, stable callback refs, resize observation, periodic ticking, and a shared event bus — so that consuming packages don't reinve
Latest version: 0.1.148

# @liberfi.io/hooks

A collection of framework-agnostic React utility hooks used across the LiberFi frontend. Provides common patterns — boolean state, stable callback refs, resize observation, periodic ticking, and a shared event bus — so that consuming packages don't reinvent them.

## Design Philosophy

- **One hook, one file** — each hook lives in its own module with a single, focused responsibility.
- **Stable references** — actions and callbacks are memoised via `useCallback` / `useRef` to prevent unnecessary consumer re-renders.
- **SSR-safe** — `useSafeLayoutEffect` guards `useLayoutEffect` behind a runtime check; other hooks use only `useEffect`.
- **Shared timer** — `useTick` multiplexes all subscribers through a single `GlobalTimer` singleton, avoiding per-hook intervals.
- **Minimal dependencies** — only `eventemitter3` and `@liberfi.io/utils` at runtime; throttle is implemented internally.

## Installation

```bash
pnpm add @liberfi.io/hooks
```

### Peer dependencies

The consumer must provide:

| Package | Version |
| ------- | ------- |
| `react` | `>=18`  |

## API Reference

### Hooks

#### `useBoolean`

Manage a boolean state with convenience setters.

```typescript
function useBoolean(initialValue?: boolean): UseBooleanReturn;

type UseBooleanReturn = [
  boolean,
  { setTrue: () => void; setFalse: () => void; toggle: () => void },
];
```

#### `useCallbackRef`

Persist a callback reference that always points to the latest function, while returning a stable callable.

```typescript
function useCallbackRef<T extends (...args: any[]) => any>(
  fn: T | undefined,
  deps?: React.DependencyList,
): T;
```

#### `useIsMounted`

Returns a function that reports whether the component is currently mounted.

```typescript
function useIsMounted(): () => boolean;
```

#### `useValueRef`

Returns a ref that always reflects the latest value (updated synchronously on render).

```typescript
function useValueRef<T>(value: T): RefObject<T>;
```

#### `useSafeLayoutEffect`

`useLayoutEffect` in the browser, `useEffect` on the server. Drop-in replacement to suppress SSR warnings.

```typescript
const useSafeLayoutEffect: typeof useLayoutEffect;
```

#### `useTick`

Calls a callback at a specified interval, powered by a shared `GlobalTimer` singleton.

```typescript
function useTick(callback: TickCallback, interval?: number): void;

type TickEvent = { delta: number; now: number };
type TickCallback = (event: TickEvent) => void;
```

| Parameter  | Type           | Default | Description                            |
| ---------- | -------------- | ------- | -------------------------------------- |
| `callback` | `TickCallback` | —       | Called each tick with `{ delta, now }` |
| `interval` | `number`       | `1000`  | Milliseconds between ticks             |

#### `useTickAge`

Returns the elapsed milliseconds since a given birthday, updated every tick.

```typescript
function useTickAge(birthday?: number | Date, interval?: number): number;
```

| Parameter  | Type             | Default      | Description         |
| ---------- | ---------------- | ------------ | ------------------- |
| `birthday` | `number \| Date` | `Date.now()` | Reference timestamp |
| `interval` | `number`         | `1000`       | Tick interval in ms |

#### `useResizeObserver`

Observes an element's size via the `ResizeObserver` API.

```typescript
function useResizeObserver<T extends HTMLElement>(
  options: UseResizeObserverOptions<T>,
): Size;
```

When `onResize` is provided, the hook delegates to it instead of updating internal state (no re-renders).

#### `useThrottledResizeObserver`

Throttled variant of `useResizeObserver`.

```typescript
function useThrottledResizeObserver<T extends HTMLElement>(
  options: UseThrottledResizeObserverOptions<T>,
): Size;
```

#### `useEventEmitter`

Returns a global `EventEmitter` singleton (from `eventemitter3`) managed by an internal DI container.

```typescript
function useEventEmitter(): EventEmitter;
```

### Types

| Type                                   | Description                                                                         |
| -------------------------------------- | ----------------------------------------------------------------------------------- |
| `UseBooleanReturn`                     | Return type of `useBoolean`                                                         |
| `Size`                                 | `{ width: number \| undefined; height: number \| undefined }`                       |
| `UseResizeObserverOptions<T>`          | Options for `useResizeObserver`: `ref`, `onResize?`, `box?`                         |
| `UseThrottledResizeObserverOptions<T>` | Options for `useThrottledResizeObserver`: `ref`, `box?`, `throttleMs?` (default 50) |
| `TickEvent`                            | Tick payload: `{ delta: number; now: number }`                                      |
| `TickCallback`                         | `(event: TickEvent) => void`                                                        |

### Constants

| Name      | Type     | Description                          |
| --------- | -------- | ------------------------------------ |
| `version` | `string` | Current package version (`"0.1.24"`) |

## Usage Examples

### Basic boolean toggle

```tsx
import { useBoolean } from "@liberfi.io/hooks";

function Panel() {
  const [isOpen, { toggle, setFalse }] = useBoolean(false);
  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      {isOpen && <div onClose={setFalse}>Content</div>}
    </div>
  );
}
```

### Periodic countdown

```tsx
import { useTickAge } from "@liberfi.io/hooks";

function Timer({ startedAt }: { startedAt: number }) {
  const elapsed = useTickAge(startedAt, 1000);
  const seconds = Math.floor(elapsed / 1000);
  return <span>{seconds}s elapsed</span>;
}
```

### Resize-aware component

```tsx
import { useRef } from "react";
import { useResizeObserver } from "@liberfi.io/hooks";

function Card() {
  const ref = useRef<HTMLDivElement>(null);
  const { width = 0 } = useResizeObserver({ ref });
  return <div ref={ref}>{width > 600 ? <WideLayout /> : <NarrowLayout />}</div>;
}
```

### Cross-component event bus

```tsx
import { useEffect } from "react";
import { useEventEmitter } from "@liberfi.io/hooks";

function Sender() {
  const ee = useEventEmitter();
  return <button onClick={() => ee.emit("refresh")}>Refresh</button>;
}

function Receiver() {
  const ee = useEventEmitter();
  useEffect(() => {
    const handler = () => console.log("refreshed");
    ee.on("refresh", handler);
    return () => {
      ee.off("refresh", handler);
    };
  }, [ee]);
  return null;
}
```

## Future Improvements

- Evaluate whether `version.ts` top-level side effect should be lazy-initialised.