Skip to content

Commit

Permalink
docs: sync
Browse files Browse the repository at this point in the history
  • Loading branch information
artalar committed Oct 20, 2023
1 parent c136bd8 commit 8ac256c
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 95 deletions.
6 changes: 3 additions & 3 deletions docs/src/content/docs/adapter/npm-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,14 @@ export const MyForm = () => {
## 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 [reatomAsyncReaction](/package/async/#reatomasyncreaction).
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](/package/async/#reatomresource).
```tsx
import { atom, reatomAsyncReaction } from '@reatom/framework'
import { atom, reatomResource } from '@reatom/framework'
import { useAtom, useAction, useAtomPromise } from '@reatom/npm-react'

const pageAtom = atom(1, 'pageAtom')
const listReaction = reatomAsyncReaction(async (ctx) => {
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)
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ https://www.patreon.com/artalar_dev

Software development in 202X is hard and we really appreciate all [contributors](https://github.com/artalar/reatom/graphs/contributors) and free software maintainers, who make our life easier. Special thanks to:

- [React](https://reactjs.org), [Redux](https://redux.js.org) and [Effector](https://effector.dev/) for inspiration
- [React](https://reactjs.org), [Redux](https://redux.js.org), [Effector](https://effector.dev/) and [$mol](https://github.com/hyoo-ru/mam_mol) for inspiration
- [microbundle](https://github.com/developit/microbundle) for handling all bundling complexity
- [Quokka](https://wallabyjs.com/oss/) and [uvu](https://github.com/lukeed/uvu) for incredible testing experience
- [TURBO](https://turbo.build) for simple monorepo management
Expand Down
20 changes: 12 additions & 8 deletions docs/src/content/docs/package/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ const reatomResource = (initState, url, concurrent = true) => {

Check the real-world example in pooling example from [story tests below](/package/async#story-test) ([src](https://github.com/artalar/reatom/blob/v3/packages/async/src/index.story.test.ts)).

## reatomAsyncReaction
## reatomResource

This method is the simplest solution to describe an asynchronous resource that is based on local states. Let's delve into the problem.

Expand All @@ -667,12 +667,12 @@ onConnect(fetchList.dataAtom, (ctx) => fetchList(ctx, ctx.get(pageAtom)))
pageAtom.onChange(fetchSuggestion) // trigger
```

`reatomAsyncReaction` allows us to use `ctx.spy` just like in the regular `atom`. It is much simpler, more obvious, and works automatically for both caching and previous request cancellation.
`reatomResource` allows us to use `ctx.spy` just like in the regular `atom`. It is much simpler, more obvious, and works automatically for both caching and previous request cancellation.

```ts
import { reatomAsyncReaction } from '@reatom/async'
import { reatomResource } from '@reatom/async'

const listReaction = reatomAsyncReaction(async (ctx) => {
const listReaction = reatomResource(async (ctx) => {
const page = ctx.spy(pageAtom)
return request(`/api/list?page=${page}`, ctx.controller)
}, 'listReaction')
Expand All @@ -682,23 +682,27 @@ Now, `listReaction` has a `promiseAtom` that you can use with [useAtomPromise](/

If you need to set up a default value and have the ability to use the resulting data, simply use `withDataAtom` as you would with any other async action.

But that's not all! The most powerful feature of `reatomAsyncReaction` is that you can use one `promiseAtom` in another, which greatly simplifies dependent request descriptions and prevents complex race conditions, as the stale promises are always automatically canceled.
But that's not all! The most powerful feature of `reatomResource` is that you can use one `promiseAtom` in another, which greatly simplifies dependent request descriptions and prevents complex race conditions, as the stale promises are always automatically canceled.

```ts
import { reatomAsyncReaction } from '@reatom/async'
import { reatomResource } from '@reatom/async'

const aReaction = reatomAsyncReaction(async (ctx) => {
const aReaction = reatomResource(async (ctx) => {
const page = ctx.spy(pageAtom)
return request(`/api/a?page=${page}`, ctx.controller)
}, 'aReaction')
const bReaction = reatomAsyncReaction(async (ctx) => {
const bReaction = reatomResource(async (ctx) => {
const a = ctx.spy(aReaction.promiseAtom)
return request(`/api/b?a=${a}`, ctx.controller)
}, 'bReaction')
```

In this example, `bReaction.pendingAtom` will be updated immediately as `aReaction` starts fetching!

## reatomAsyncReaction

> Deprecated: use [reatomResource](#reatomresource) instead
## Story test

[source](https://github.com/artalar/reatom/blob/v3/packages/async/src/index.story.test.ts)
Expand Down
51 changes: 45 additions & 6 deletions docs/src/content/docs/package/effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,28 @@ Two important notes.

### take

Allow you to wait an atom update.
This is the simplest and most powerful API that allows you to wait for an atom update, which is useful for describing certain procedures. It is a shortcut for subscribing to the atom and unsubscribing after the first update. `take` respects the main Reatom abort context and will throw `AbortError` when the abort occurs. This allows you to describe redux-saga-like procedural logic in synchronous code style with native async/await.

```ts
import { action } from '@reatom/core'
import { take } from '@reatom/effects'

const currentCount = ctx.get(countAtom)
const nextCount = await take(ctx, countAtom)
export const validateBeforeSubmit = action(async (ctx) => {
let errors = validate(ctx.get(formDataAtom))

while (Object.keys(errors).length) {
formDataAtom.errorsAtom(ctx, errors)
// wait any field change
await take(ctx, formDataAtom)
// recheck validation
errors = validate(ctx.get(formDataAtom))
}
})
```

You could await actions too!
You can also await actions!

```ts
// ~/features/someForm.ts
import { take } from '@reatom/effects'
import { onConnect } from '@reatom/hooks'
import { historyAtom } from '@reatom/npm-history'
Expand All @@ -61,7 +70,7 @@ import { confirmModalAtom } from '~/features/modal'
// some model logic, doesn't matter
export const formAtom = reatomForm(/* ... */)

onConnect(form, (ctx) => {
onConnect(formAtom, (ctx) => {
// "history" docs: https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md
const unblock = historyAtom.block(ctx, async ({ retry }) => {
if (!ctx.get(formAtom).isSubmitted && !ctx.get(confirmModalAtom).opened) {
Expand All @@ -78,6 +87,36 @@ onConnect(form, (ctx) => {
})
```

#### take filter

You can pass the third argument to map the update to the required format.

```ts
const input = await take(ctx, onChange, (ctx, event) => event.target.value)
```

More than that, you can filter unneeded updates by returning the `skip` mark from the first argument of your callback.

```ts
const input = await take(ctx, onChange, (ctx, event, skip) => {
const { value } = event.target
return value.length < 6 ? skip : value
})
```

The cool feature of this skip mark is that it helps TypeScript understand the correct type of the returned value, which is hard to achieve with the extra "filter" function. If you have a union type, you could receive the needed data with the correct type easily. It just works.

```ts
const someRequest = reatomRequest<{ data: Data } | { error: string }>()
```

```ts
// type-safe destructuring
const { data } = await take(ctx, someRequest, (ctx, payload, skip) =>
'error' in payload ? skip : payload,
)
```

### takeNested

Allow you to wait all dependent effects, event if they was called in the nested async effect.
Expand Down
108 changes: 72 additions & 36 deletions docs/src/content/docs/package/lens.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,52 +193,47 @@ export const countAtom = _countAtom.pipe(readonly)

## `parseAtoms`

Jsonify [atomized](/guides/atomization) structure. Needed for parse values of deep structures with nested atoms.
Useful for snapshots. Will be reactive if the passed `ctx` is `CtxSpy`.
Recursively unwrap all atoms in an [atomized](/guides/atomization) structure. Useful for making snapshots of reactive state. Uses `ctx.spy` if it's available.

### parseAtoms snapshot example
### `parseAtoms`: persistence example

https://codesandbox.io/s/reatom-react-atomization-k39vrs?file=/src/model.ts

```ts
import { action, atom, Action, AtomMut } from '@reatom/core'
import { onUpdate, withInit } from '@reatom/hooks'
import { withLocalStorage } from '@reatom/persist-web-storage'
import { parseAtoms, ParseAtoms } from '@reatom/lens'

export type Field = {
id: number;
name: string;
value: AtomMut<string>;
remove: Action;
};

const KEY = "FIELDS";
const fromLS = () => {
const snap = localStorage.getItem(KEY);
if (!snap) return [];
const json: ParseAtoms<Array<Field>> = JSON.parse(snap);
return json.map(({ id, name, value }) => getField(id, name, value));
};
const toLS = action((ctx) => {
const list = parseAtoms(ctx, listAtom);
localStorage.setItem(KEY, JSON.stringify(list));
}, "toLS");
id: number
name: string
value: AtomMut<string>
remove: Action
}

const getField = (id: number, name: string, value: string): Field => {
// ...
};
return {
id,
name,
value: atom(value),
remove: action((ctx) => {
// ...
}),
}
}

export const listAtom = atom(new Array<Field>(), "listAtom").pipe(
withInit(fromLS)
);
onUpdate(listAtom, toLS);
export const listAtom = atom<Array<Field>>([], 'listAtom').pipe(
withLocalStorage({
toSnapshot: (state) => parseAtoms(state),
fromSnapshot: (snapshot: any) =>
getField(snapshot.id, snapshot.name, snapshot.value),
}),
)
```

### parseAtoms shortcut example

It could be handy to use `parseAtoms` to reduce the amount of "read atom" code.
### `parseAtoms`: shortcut example

For example, we have a few-fields structure.
You can use `parseAtoms` to reduce the amount of . Let's suppose you have the following structure:

```ts
interface User {
Expand All @@ -249,7 +244,7 @@ interface User {
}
```

How could you display it without `parseAtoms`?
And use it like this:

```tsx
import { useAtom } from '@reatom/npm-react'
Expand All @@ -264,21 +259,62 @@ export const User = ({ user }: { user: User }) => {
}
```

How could `parseAtoms` helps you?
With `parseAtoms` you can refactor usage to look like this:

```tsx
import { parseAtoms } from '@reatom/lens'
import { useAtom, useAction } from '@reatom/npm-react'

export const User = ({ user }: { user: User }) => {
const [{ name, bio, website, address }] = useAtom((ctx) =>
parseAtoms(ctx, user),
)
const [
{
name, //
bio,
website,
address,
},
] = useAtom((ctx) => parseAtoms(ctx, user))

return <form>...</form>
}
```

## `match`

Creates an atom that depending on some condition, which can be an atom too. Useful for describing UIs with [`@reatom/jsx`](/package/jsx).

```ts
const num = atom(0, 'num')
const by3 = atom((ctx) => ctx.spy(num) % 3 === 0, 'by3')
const by5 = atom((ctx) => ctx.spy(num) % 5 === 0, 'by5')

const failMessage = atom(
(ctx) => `${ctx.spy(num)} is not divisible by 3 nor by 5`,
)

const message = match(
by3,
'Divisible by 3',
match(
by5, //
'Not divisible by 3 but divisible by 5',
failMessage,
),
)
```

### `match` JSX example

```tsx
export function Dashboard({ user }: { user: Atom<User | null> }) {
return match(
user,
atom((ctx) => <div>Dashboard content</div>),
atom((ctx) => <AuthRedirect />),
)
}
```

## `bind`

Bind context to stable function.
Expand Down
29 changes: 2 additions & 27 deletions docs/src/content/docs/package/persist-web-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Reatom adapter for localStorage and sessionStorage
<!-- THIS FILE WAS AUTOGENERATED -->
<!-- DO NOT EDIT THIS FILE -->
<!-- CHECK "packages/*/README.md" -->
Simple and powerful persist adapter to make your state leave after the application exit. It allows your atom to initiate with data from a storage, sync the updates with a storage and subscribe to a storage updates - useful for tabs synchronization.
[`@reatom/persist`](/package/persist) adapter for the Web Storage APIs.

## Installation

Expand All @@ -15,28 +15,7 @@ npm i @reatom/persist-web-storage

## Usage

There a re two similar types adapters for each storage: `withLocalStorage`, `withSessionStorage`.

The number of options should could control:

```ts
interface WithPersistOptions<T> {
/** parse data on init or subscription update @optional */
fromSnapshot?: Fn<[ctx: Ctx, snapshot: unknown, state?: T], T>
/** the key! */
key: string
/** migration callback which will be called if the version changed @optional */
migration?: Fn<[ctx: Ctx, persistRecord: PersistRecord], T>
/** turn on/off subscription @default true */
subscribe?: boolean
/** time to live in milliseconds @default 10 ** 10 */
time?: number
/** transform data before persisting @optional */
toSnapshot?: Fn<[ctx: Ctx, state: T], unknown>
/** version of the data which change used to trigger the migration @default 0 */
version?: number
}
```
There are two similar types adapters for each storage: `withLocalStorage`, `withSessionStorage`.

## Simple example

Expand All @@ -46,7 +25,3 @@ import { withLocalStorage } from '@reatom/persist-web-storage'

export const tokenAtom = atom('', 'tokenAtom').pipe(withLocalStorage('token'))
```

## Testing

It is simple to test data with persist adapter as we have a special util for that: /package/testing#createmockstorage
Loading

1 comment on commit 8ac256c

@vercel
Copy link

@vercel vercel bot commented on 8ac256c Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.