npm package: @reatom/npm-react
Description: Reatom adapter for React
Author: artalar
Latest version: 3.10.6

Adapter for [react](https://github.com/facebook/react).

## Installation

```sh
npm i @reatom/npm-react
```

Also, you need to be installed `@reatom/core` or `@reatom/framework` and `react`.

> Read [the handbook](https://www.reatom.dev/handbook) first for production usage.

## Setup context

You need to set up the main context once and wrap your application in a provider at the top level.

```jsx
import { createCtx, connectLogger } from '@reatom/framework'
import { reatomContext } from '@reatom/npm-react'
import { Main } from './path/to/an/Main'

const ctx = createCtx()
if (import.meta.env.DEV) {
  connectLogger(ctx)
}

export const App = () => (
  <reatomContext.Provider value={ctx}>
    <Main />
  </reatomContext.Provider>
)
```

## Use atom

### reatomComponent

The main API to bind atoms and actions to a component lifetime is `reatomComponent`. It wraps your regular react component and put `ctx` into the props. There is no additional rules or behavior, you can use any other hooks, accept props, return any valid `ReactNode`. But if you using `ctx.spy`, just like in any computed atom, it will subscribe to the passed atom and rerender from by changes.

```tsx
import { atom } from '@reatom/core'
import { reatomComponent } from '@reatom/npm-react'

export const countAtom = atom(0)
export const Counter = reatomComponent(
  ({ ctx }) => (
    <input
      type="number"
      value={ctx.spy(count)}
      onChange={(e) => countAtom(ctx, e.target.valueAsNumber)}
    />
  ),
  'Counter',
)
```

You can describe props types in the generic, it can be any kind of values, regular string, JSON, and atoms too. For example, here is a controlled component with atom state. Also, you can use additional `bind` method instead of [`useAction`](#use-action) to bind an action to the component.

```tsx
import { atom, Atom } from '@reatom/core'
import { reatomComponent } from '@reatom/npm-react'

export const Counter = reatomComponent<{
  atom: Atom<number>
  onChange: Action
}>(
  ({ ctx, atom, onChange }) => (
    <input type="number" value={ctx.spy(atom)} onChange={ctx.bind(onChange)} />
  ),
  'Counter',
)
```

One of the most powerful features of `reatomComponent` is that you are not bound by react hooks rules, you could use `ctx.spy` in any order, right in your template.

```tsx
export const SomeList = reatomComponent(
  ({ ctx }) =>
    ctx.spy(isLoadingAtom) ? (
      <span>Loading...</span>
    ) : (
      <ul>
        {ctx.spy(listAtom).map((el) => (
          <li>{el.text}</li>
        ))}
      </ul>
    ),
  'SomeList',
)
```

Do not forget to put the component name to the second argument, it will increase your feature debug experience a lot!

#### Unmount

An important feature of `reatomComponent` is automatic resource management with default for Reatom AbortController in the cause context. You may be familiar with this concept from [@reatom/effects](https://www.reatom.dev/package/effects/). The `ctx` in reatomComponent props includes the AbortController which is followed by all derived actions. For example, it means if you will update an atom from the component and it will cause [reatomResource](https://www.reatom.dev/package/async/#reatomresource) refetch and the component will unmaunt before the fetch end - the fetch will throw an abort error.

This increases the stability of your application as it reduces the amount of possible race conditions. But be aware that sometimes you may want to create a request that you don't want to abort even if the unmount occurs. For example, it might be an analytic event, in which case you should use [spawn](https://www.reatom.dev/package/effects/#spawn).

### useAtom

`useAtom` is your main hook, when you need to describe reusable logic in hight order hook. It accepts an atom to read it value and subscribes to the changes, or a primitive value to create a new mutable atom and subscribe to it. It alike `useState`, but with many additional features. It returns a tuple of `[state, setState, theAtom, ctx]`. `theAtom` is a reference to the passed or created atom.

In a component:

```tsx
import { action, atom } from '@reatom/core'
import { useAction, useAtom } from '@reatom/npm-react'

// base mutable atom
const inputAtom = atom('', 'inputAtom')
// computed readonly atom
const greetingAtom = atom(
  (ctx) => `Hello, ${ctx.spy(inputAtom)}!`,
  'greetingAtom',
)
// action to do things
const onChange = action(
  (ctx, event: React.ChangeEvent<HTMLInputElement>) =>
    inputAtom(ctx, event.currentTarget.value),
  'onChange',
)

export const Greeting = () => {
  const [input] = useAtom(inputAtom)
  const [greeting] = useAtom(greetingAtom)
  const handleChange = useAction(onChange)

  return (
    <>
      <input value={input} onChange={handleChange} />
      {greeting}
    </>
  )
}
```

We recommend to setup [logger](https://www.reatom.dev/package/logger) here.

## Use atom selector

Another use case for the hook is describing additional computations inside a component (create temporal computed atom). It is possible to put a reducer function to `useState`, which will create a new computed atom (`setState` will be `undefined` in this case).

```ts
import { useAtom } from '@reatom/npm-react'
import { goodsAtom } from '~/goods/model'

export const GoodsItem = ({ idx }: { idx: number }) => {
  const [element] = useAtom((ctx) => ctx.spy(goodsAtom)[idx], [idx])

  return <some-jsx {...element} />
}
```

The reducer function is just the same as in `atom` function. You could `spy` a few other atoms. It will be called only when the dependencies change, so you could use conditions and Reatom will optimize your dependencies and subscribes only to the necessary atoms.

```ts
import { useAtom } from '@reatom/npm-react'
import { activeAtom, goodsAtom } from '~/goods/model'

export const GoodsItem = ({ idx }: { idx: number }) => {
  const [element] = useAtom(
    (ctx) => (ctx.spy(activeAtom) === idx ? ctx.spy(listAtom)[idx] : null),
    [idx],
  )

  if (!element) return null

  return <some-jsx {...element} />
}
```

### Advanced usage

Check this out!

```js
export const Greeting = ({ initialGreeting = '' }) => {
  const [input, setInput, inputAtom] = useAtom(initialGreeting)
  const [greeting] = useAtom(
    (ctx) => `Hello, ${ctx.spy(inputAtom)}!`,
    [inputAtom],
  )
  // you could do this
  const handleChange = useCallback(
    (event) => setInput(event.currentTarget.value),
    [setInput],
  )
  // OR this
  const handleChange = useAction(
    (ctx, event) => inputAtom(ctx, event.currentTarget.value),
    [inputAtom],
  )

  return (
    <>
      <input value={input} onChange={handleChange} />
      {greeting}
    </>
  )
}
```

What, why? In the example bellow we creating "inline" atoms, which will live only during the component lifetime. Here are the benefits of this pattern instead of using regular hooks:

- You could depend your atoms by a props (deps changing will cause the callback rerun, the atom will the same).
- Easy access to services, in case you use reatom as a DI.
- Component inline atoms could be used for other computations, which could prevent rerenders ([see above](#prevent-rerenders)).
- Created actions and atoms will be visible in logger / debugger with async `cause` tracking, which is much better for debugging than `useEffect`.
- Unify codestyle for any state (local and global) description.
- Easy to refactor to global state.

### Lazy reading

[As react docs says](https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback), sometimes you need a callback, which depends on often changed value, but you don't want to change a reference of this handler, to not broke memoization of children components which depends on the current. In this case, you could use atom and read it value lazily.

Here is a standard react code, `handleSubmit` reference is recreating on each `input` change and rerender.

```js
const [input, setInput] = useState('')
const handleSubmit = useCallback(
  () => props.onSubmit(input),
  [props.onSubmit, input],
)
```

Here `handleSubmit` reference is stable and doesn't depend on `input`, but have access to it last value.

```js
const [input, setInput, inputAtom, ctx] = useAtom('')
const handleSubmit = useCallback(
  () => props.onSubmit(ctx.get(inputAtom)),
  [props.onSubmit, inputAtom, ctx],
)
```

Btw, you could use `useAction`.

```js
const [input, setInput, inputAtom] = useAtom('')
const handleSubmit = useAction(
  (ctx) => props.onChange(ctx.get(inputAtom)),
  [props.onChange, inputAtom],
)
```

### Prevent rerenders

`useAtom` accepts third argument `shouldSubscribe` which is `true` by default. But sometimes you have a set of computations not all of which you need in the render. In this case you could use atoms from `useAtom` without subscribing to it values.

Here is how could you share data created and managed in parent, but used in children.

```ts
const [filter, setFilter, filterAtom] = useAtom('', [], false)
const [data, setData, dataAtom] = useAtom([], [], false)
const handleSubmit = useAction(
  (ctx) =>
    ctx.schedule(() =>
      fetch(`api/search?q=${ctx.get(filterAtom)}`)
        .then((res) => res.json())
        .then(setData),
    ),
  [filterAtom, dataAtom],
)

return (
  <>
    <Filter atom={filterAtom} />
    <Table atom={dataAtom} />
    {/* this will not rerender by filters or data changes */}
    <OtherComponent />
  </>
)
```

Here is another example of in-render computations which could be archived without rerender.

[![codesandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/elegant-forest-w2106l?file=/src/App.tsx)

```js
// this component will not rerender by `inputAtom` change, only by `numbers` change
const [, , inputAtom] = useAtom('', [], false)
const handleChange = useAction(
  (ctx, event) => inputAtom(ctx, event.currentTarget.value),
  [inputAtom],
)
const [numbers] = useAtom(
  (ctx) => ctx.spy(inputAtom).replace(/\D/g, ''),
  [inputAtom],
)

return (
  <>
    <input onChange={handleChange} />
    numbers: {numbers}
  </>
)

// onChange "q" - no rerender
// onChange "qw" - no rerender
// onChange "qw1" - rerender
// onChange "qw1e" - no rerender
```

## Use action

To bind your actions to relative context you need to use `useAction`, it will just remove the first `ctx` parameter from your action and return a function which accepts all other needed parameters.

```tsx
const pageAtom = atom(0, 'pageAtom')
const next = action((ctx) => pageAtom(ctx, (page) => page + 1), 'pageAtom.next')
const prev = action(
  (ctx) => pageAtom(ctx, (page) => Math.max(1, page - 1)),
  'pageAtom.prev',
)

export const Paging = () => {
  const [page] = useAtom(pageAtom)
  const handleNext = useAction(next)
  const handlePrev = useAction(prev)

  return (
    <>
      <button onClick={handlePrev}>prev</button>
      {page}
      <button onClick={handleNext}>next</button>
    </>
  )
}
```

`useAction` accepts any function with `ctx` parameter, not only `action`, so you can write inline function, use props, and it will still memoized and return the same stable function reference, just like [useEvent](https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md)

```tsx
export const Paging = ({ pageAtom }: { pageAtom: Atom<number> }) => {
  const [page] = useAtom(pageAtom)
  const handleNext = useAction((ctx) => pageAtom(ctx, (page) => page + 1))
  const handlePrev = useAction((ctx) =>
    pageAtom(ctx, (page) => Math.max(1, page - 1)),
  )

  return (
    <>
      <button onClick={handlePrev}>prev</button>
      {page}
      <button onClick={handleNext}>next</button>
    </>
  )
}
```

Also, you can use `useAction` to get an atom setter without subscribing to it.

```tsx
export const PagingAction = ({ pageAtom }: { pageAtom: Atom<number> }) => {
  const setPage = useAction(pageAtom)

  return (
    <>
      <button onClick={() => setPage((page) => Math.max(1, page - 1))}>
        prev
      </button>
      <button onClick={() => setPage((page) => page + 1)}>next</button>
    </>
  )
}
```

## Use update

`useUpdate` is a similar to `useEffect` hook, but it allows you to subscribe to atoms and receive it values in the callback. Important semantic difference is that subscription to atoms works as [`onChange` hook](https://www.reatom.dev/handbook#lifecycle) and your callback will call during transaction, so you need to schedule an effects, but could mutate an atoms without batching. Subscriptions to a values works like regular `useEffect` hook.

The most common use case for this hook is to synchronize some state from a props or context to an atom.

```tsx
import { action, atom } from '@reatom/core'
import { useAction, useUpdate } from '@reatom/npm-react'
import Form from 'form-library'

const formValuesAtom = atom({})
const submit = action((ctx) => api.submit(ctx.get(formValuesAtom)))

const Sync = () => {
  const { values } = useFormState()
  useUpdate((ctx, values) => formValuesAtom(ctx, values), [values])
  return null
}
// or just
const Sync = () => useUpdate(formValuesAtom, [useFormState().values])

export const MyForm = () => {
  const handleSubmit = useAction(submit)

  return (
    <Form onSubmit={handleSubmit}>
      <Sync />
      .....
    </Form>
  )
}
```

And it works well in the opposite direction, you could synchronise an atom's data with the local state, or do any other kind of effect. You can use `useUpdate` as a safety replacement for `onChange` + `useEffect`.

For example, you need a controlled input from the passed atom.

Here is a naive implementation:

```tsx
export const Item = ({ itemAtom }) => {
  const [value, setValue] = React.useState('')

  React.useEffect(() => {
    const cleanup = itemAtom.onChange((ctx, state) => setValue(state))
    // DO NOT FORGET TO RETURN THE CLEANUP
    return cleanup
  }, [itemAtom])

  return (
    <input value={value} onChange={(e) => setValue(e.currentTarget.value)} />
  )
}
```

Here is a simpler and more reliable implementation:

```tsx
export const Item = ({ itemAtom }) => {
  const [value, setValue] = React.useState(itemAtom)

  useUpdate((ctx, state) => setValue(state), [itemAtom])

  return (
    <input value={value} onChange={(e) => setValue(e.currentTarget.value)} />
  )
}
```

## Use atom promise

If you have an atom with a promise and want to use its value directly, you could use `useAtomPromise`. This function relies on [React Suspense](https://react.dev/reference/react/Suspense) and throws the promise until it resolves. It can be useful with [reatomResource](https://www.reatom.dev/package/async/#reatomresource).

```tsx
import { atom, reatomResource } from '@reatom/framework'
import { useAtom, useAction, useAtomPromise } from '@reatom/npm-react'

const pageAtom = atom(1, 'pageAtom')
const listReaction = reatomResource(async (ctx) => {
  const page = ctx.spy(pageAtom)
  const response = await ctx.schedule(() => fetch(`/api/list?page=${page}`))
  if (!response.ok) throw new Error(response.statusText)
  return response.json()
})

export const List = () => {
  const [page] = useAtom(pageAtom)
  const prev = useAction((ctx) =>
    pageAtom(ctx, (state) => Math.max(1, state - 1)),
  )
  const next = useAction((ctx) => pageAtom(ctx, (state) => state + 1))
  const list = useAtomPromise(listReaction.promiseAtom)

  return (
    <section>
      <ul>
        {list.map((el) => (
          <li key={el.id}>...</li>
        ))}
      </ul>
      <hr />
      <button onClick={prev}>prev</button>
      {page}
      <button onClick={next}>next</button>
    </section>
  )
}
```

## Use context creator

Sometimes, you can only create `ctx` inside a React component, for example, in SSR. For that case, we have the `useCreateCtx` hook.

```tsx
export const App = () => {
  const ctx = useCreateCtx((ctx) => {
    // do not use logger in a server (SSR)
    if (typeof window !== 'undefined') {
      connectLogger(ctx)
    }
  })

  return (
    <reatomContext.Provider value={ctx}>
      <Component {...pageProps} />
    </reatomContext.Provider>
  )
}
```

## Examples

- [Migration from RTK to Reatom](https://github.com/artalar/RTK-entities-basic-example/pull/1/files#diff-43162f68100a9b5eb2e58684c7b9a5dc7b004ba28fd8a4eb6461402ec3a3a6c6) (2 times less code, -8kB gzip)

## Setup batching for old React

For React 16 and 17 you need to setup batching by yourself in the root of your app.

For `react-dom`:

```js
import { unstable_batchedUpdates } from 'react-dom'
import { createCtx } from '@reatom/core'
import { setupBatch, withBatching } from '@reatom/npm-react'

setupBatch(unstable_batchedUpdates)
const ctx = withBatching(createCtx())
```

For `react-native`:

```js
import { unstable_batchedUpdates } from 'react-native'
import { createCtx } from '@reatom/core'
import { setupBatch } from '@reatom/npm-react'

setupBatch(unstable_batchedUpdates)
const ctx = withBatching(createCtx())
```