# atom.io/react

Source: docs/source/pages/docs/react.mdx
URL: /docs/react

# <low-emphasis>atom.io</low-emphasis>/react

## package contents

<table-wrapper>

| Export | Description |
| --- | --- |
| `useI` | Make an updater function for a reactive variable. |
| `useO` | Observe a reactive variable. |
| `useJSON` | Observe the JSON form of a mutable atom. |
| `useTL` | Control a timeline or keyed timeline-family member and observe its state. |
| `useLoadable` | For a `Loadable` atom or selector, this hook provides a convenient interface for observing its value, loading state, and error state. |
| `StoreProvider` | Supply the store used by descendant atom.io React hooks. |
| `StoreContext` | Access the current store when building advanced integrations. |

</table-wrapper>

## StoreProvider

`StoreProvider` selects the store used by atom.io hooks in its descendant React
tree. Its optional `store` prop accepts a store such as `silo.store`. When the prop
is omitted—or when there is no provider—the hooks use atom.io's implicit store.

The standard hooks, including `useO`, `useI`, `useJSON`, `useTL`, and `useLoadable`,
read this context. You do not need to wrap them in custom, context-aware adapters to
use an explicit store.

This example creates two [Silos](/docs/concepts#silo). Both have a `title` atom, but
each provider resolves that token key in its own store. Editing one input leaves the
other document unchanged.

### silo store providers
Source: docs/source/exhibits/react/silo-store-providers.tsx

```tsx
import { Silo } from "atom.io"
import { StoreProvider, useI, useO } from "atom.io/react"
import type { JSX } from "react/jsx-runtime"

function createDocumentState(name: string) {
	const silo = new Silo({
		name,
		lifespan: `ephemeral`,
		isProduction: false,
	})
	const titleAtom = silo.atom<string>({
		key: `title`,
		default: `Untitled`,
	})
	return { silo, titleAtom }
}

const leftDocument = createDocumentState(`left-document`)
const rightDocument = createDocumentState(`right-document`)

function TitleEditor(props: { document: typeof leftDocument }) {
	const title = useO(props.document.titleAtom)
	const setTitle = useI(props.document.titleAtom)

	return (
		<input
			aria-label={`${props.document.silo.store.config.name} title`}
			value={title}
			onChange={(event) => {
				setTitle(event.currentTarget.value)
			}}
		/>
	)
}

export function DocumentWorkspace(): JSX.Element {
	return (
		<>
			<StoreProvider store={leftDocument.silo.store}>
				<TitleEditor document={leftDocument} />
			</StoreProvider>
			<StoreProvider store={rightDocument.silo.store}>
				<TitleEditor document={rightDocument} />
			</StoreProvider>
		</>
	)
}
```

Keep each provider's store stable for the lifetime of its mounted subtree. Create a
Silo outside render or with a stable React initializer. Replacing the `store` prop on
an already-mounted `StoreProvider` is not currently supported. To switch stores,
remount the provider subtree, usually by giving it a key that identifies the store.

For production isolation patterns and lifecycle guidance, see the
[Silo guide](/docs/concepts#silo).

### StoreContext

`StoreContext` is the context read by `StoreProvider` and atom.io's hooks. It is a
public, advanced API for integrations that need direct access to the current store,
such as developer tools or binding libraries. Application components should prefer
`StoreProvider` and the standard hooks.

Like `StoreProvider`, `StoreContext` defaults to the implicit store.

## useO

`useO`—named in the sense "use output"—is a React hook that returns the current value of an atom or selector in a React component (and dispatches a re-render whenever there is a new value).

### use o
Source: docs/source/exhibits/react/use-o.tsx

```tsx
import { atom } from "atom.io"
import { useO } from "atom.io/react"

function discoverUrl() {
	return new URL(window.location.href)
}
const urlAtom = atom<string>({
	key: `url`,
	default: () => discoverUrl().toString(),
	effects: [
		({ setSelf }) => {
			const syncFromBrowser = () => {
				setSelf(discoverUrl().toString())
			}
			window.addEventListener(`popstate`, syncFromBrowser)
			return () => {
				window.removeEventListener(`popstate`, syncFromBrowser)
			}
		},
	],
})

function UrlDisplay() {
	const url = useO(urlAtom)
	return <div>{url}</div>
}
```

## useI

`useI`—named in the sense "use input"—is a React hook that returns a stable setter
callback. For atoms, that callback accepts a value or updater just like
[`setState`](/docs/atom-io#get-and-set-an-atom-ts). For writable selectors, it passes
the new value to the selector's `set` callback so the selector can update its
dependencies.

### use i
Source: docs/source/exhibits/react/use-i.tsx

```tsx
import { atom } from "atom.io"
import { useI, useO } from "atom.io/react"

const toggleAtom = atom<boolean>({
	key: `toggle`,
	default: false,
})

function ToggleInput() {
	const setToggle = useI(toggleAtom)
	const toggle = useO(toggleAtom)
	return (
		<input
			type="checkbox"
			checked={toggle}
			onChange={() => {
				setToggle((t) => !t)
			}}
		/>
	)
}
```

## useJSON

`useJSON` is a React hook that makes working with mutable atoms in your React components more convenient.

Mutable atoms hold transceivers, such as `UList` or `OList`. Those values are
useful when you are updating them, but they are usually not the shape you want to render
directly.

If you want more background on transceivers, see [the transceivers guide](/transceivers).

`useJSON` observes the mutable atom's JSON view instead. For example, a `UList<string>`
becomes a readonly `string[]`, which is usually easier to map over in JSX.

### use json
Source: docs/source/exhibits/react/use-json.tsx

```tsx
import { mutableAtom } from "atom.io"
import { useI, useJSON } from "atom.io/react"
import { UList } from "atom.io/transceivers/u-list"

const selectedTagKeysAtom = mutableAtom<UList<string>>({
	key: `selectedTagKeys`,
	class: UList,
})

function SelectedTags() {
	const selectedTagKeys = useJSON(selectedTagKeysAtom)
	const setSelectedTagKeys = useI(selectedTagKeysAtom)

	return (
		<>
			<button
				type="button"
				onClick={() => {
					setSelectedTagKeys((tagKeys) => {
						tagKeys.add(`typescript`)
						return tagKeys
					})
				}}
			>
				Add TypeScript
			</button>
			{selectedTagKeys.map((tagKey) => (
				<div key={tagKey}>{tagKey}</div>
			))}
		</>
	)
}
```

Use `useJSON` when you want to render a mutable atom. Use `useI` when you want to update
one.

## useTL

`useTL` provides convenient access to the `undo` and `redo` utilities, as well as metadata representing how many events are on the timeline (`length`) and where the timeline is currently positioned (`at`).

### use tl
Source: docs/source/exhibits/react/use-tl.tsx

```tsx
import { useTL } from "atom.io/react"

import { coordinatesTimeline } from "../core/timeline/create-a-timeline.ts"

export function UrlDisplay(): React.JSX.Element {
	const { at, length, undo, redo } = useTL(coordinatesTimeline)
	return (
		<>
			<div>{at}</div>
			<div>{length}</div>
			<button type="button" onClick={undo}>
				undo
			</button>
			<button type="button" onClick={redo}>
				redo
			</button>
		</>
	)
}
```

Pass a timeline family and key to control one lazily created member. When the key
changes, `useTL` switches to that member's history. The member is always resolved in
the current `StoreProvider` store.

### use timeline family
Source: docs/source/exhibits/react/use-timeline-family.tsx

```tsx
import { useTL } from "atom.io/react"

import { glyphTimelines } from "../core/timeline/create-a-timeline-family.ts"

export function GlyphHistory(props: { glyphId: string }): React.JSX.Element {
	const { at, length, undo, redo, clear } = useTL(glyphTimelines, props.glyphId)

	return (
		<>
			<span>
				{at} / {length}
			</span>
			<button type="button" disabled={at === 0} onClick={undo}>
				undo
			</button>
			<button type="button" disabled={at === length} onClick={redo}>
				redo
			</button>
			<button type="button" onClick={clear}>
				clear history
			</button>
		</>
	)
}
```

## useLoadable

If you have a `Loadable` atom or selector, `useLoadable` is a great way to observe it in your React components.

### use loadable bare
Source: docs/source/exhibits/react/use-loadable-bare.tsx

```tsx
import { atom, type Loadable } from "atom.io"
import { useLoadable } from "atom.io/react"

const myApiDataAtom = atom<Loadable<{ name: string }>>({
	key: `myApiData`,
	default: async () => {
		const response = await fetch(`https://api.github.com/users/jeremybanka`)
		return response.json()
	},
})

export function UrlDisplay(): React.JSX.Element {
	const myApiData = useLoadable(myApiDataAtom)
	if (myApiData === `LOADING`) {
		return <p>Loading...</p>
	}
	return (
		<div>
			<h1>
				{myApiData.value.name}
				{myApiData.loading ? ` ⌛` : ``}
			</h1>
		</div>
	)
}
```

In this example, we can see that the hook will return the string `"LOADING"` until the promise resolves. Then, it will return an object containing `value` with our data, and `loading` indicating whether new data is on its way.

### use loadable fallback
Source: docs/source/exhibits/react/use-loadable-fallback.tsx

```tsx
import { atom, type Loadable } from "atom.io"
import { useLoadable } from "atom.io/react"

const myApiDataAtom = atom<Loadable<{ name: string }>, Error>({
	key: `myApiData`,
	default: async () => {
		const response = await fetch(`https://api.github.com/users/jeremybanka`)
		return response.json()
	},
	catch: [Error],
})

export function UrlDisplay(): React.JSX.Element {
	const myApiData = useLoadable(myApiDataAtom, { name: `Jeremy Banka` })
	return (
		<div>
			<h1>
				{myApiData.value.name}
				{myApiData.loading ? ` ⌛` : ``}
			</h1>
			{myApiData.error ? <p>{myApiData.error.message}</p> : null}
		</div>
	)
}
```

If you'd rather assume your data is loaded, simply pass a fallback parameter with same type as your loaded data.

The fallback form always returns an object. You do not have to handle the bare
`"LOADING"` sentinel, because `value` is backed by your fallback until real data arrives.
The object still includes `loading`, and it can include `error` when a caught load fails.

### loadable ownership

A component should observe a `Loadable`, not start or reset it on mount.

When a `Loadable` atom or family member has an async default, the first
`useLoadable` or `useO` read starts the work and caches the pending promise. Other
components and selectors that read the same token share that work.

Do not pair `useLoadable(queryAtom)` with a React mount effect that resets the same
atom:

### reset on mount
Source: docs/source/exhibits/react/reset-loadable-on-mount.tsx#reset-loadable-on-mount

```tsx
useEffect(() => {
	resetState(queryAtom) // ❌ do not reset on mount; duplicates async work
}, [])
```

`resetState` recomputes the async default, so it starts new async work. In React
development `StrictMode`, mount effects may run more than once, which can turn one
intended read into multiple network requests.

Use `resetState` from explicit refresh actions, mutation flows, retry flows, or test
setup. For route params and other request inputs, prefer an `atomFamily` keyed by the
input and observe the family member directly.

Read is hydration. Key is identity. Reset is invalidation.

### error handling

If the atom or selector declares a `catch` option, `useLoadable`'s
fallback form gives you a typed `error?: E` property alongside the fallback-backed
`value`.

### use loadable catch
Source: docs/source/exhibits/react/use-loadable-catch.tsx

```tsx
import { atom, type Loadable } from "atom.io"
import { useLoadable } from "atom.io/react"

class RequestError extends Error {
	public readonly status: number

	public constructor(status: number, message: string) {
		super(message)
		this.name = `RequestError`
		this.status = status
	}
}

const accountAtom = atom<Loadable<{ name: string }>, RequestError>({
	key: `account`,
	default: async () => {
		await Promise.resolve()
		throw new RequestError(503, `Service unavailable`)
	},
	catch: [RequestError],
})

export function AccountCard(): React.JSX.Element {
	const account = useLoadable(accountAtom, { name: `Guest` })
	return (
		<div>
			<h1>
				{account.value.name}
				{account.loading ? ` ⌛` : ``}
			</h1>
			{account.error ? (
				<p>
					{account.error.status}: {account.error.message}
				</p>
			) : null}
		</div>
	)
}
```

In this example, `value` stays usable as `{ name: string }`, while `error` is available as
`RequestError | undefined`. That lets your component keep rendering with fallback data
while still reacting to the handled failure case.

See [/docs/atom-io#catching](/docs/atom-io#catching-errors) for how to declare that error channel on atoms and selectors.
