diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts index f7dff7c9d..7f085819f 100644 --- a/docs/.astro/types.d.ts +++ b/docs/.astro/types.d.ts @@ -201,7 +201,6 @@ declare module 'astro:content' { type ContentEntryMap = { "docs": { -<<<<<<< HEAD "adapter/npm-cookie-baker.md": { id: "adapter/npm-cookie-baker.md"; slug: "adapter/npm-cookie-baker"; @@ -237,8 +236,6 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -======= ->>>>>>> 2505f77 (docs: fix build after re-arrange) "compat/core-v1.md": { id: "compat/core-v1.md"; slug: "compat/core-v1"; @@ -330,176 +327,170 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/async.md": { - id: "packages/async.md"; - slug: "packages/async"; +"package/async.md": { + id: "package/async.md"; + slug: "package/async"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/effects.md": { - id: "packages/effects.md"; - slug: "packages/effects"; +"package/effects.md": { + id: "package/effects.md"; + slug: "package/effects"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/eslint-plugin.md": { - id: "packages/eslint-plugin.md"; - slug: "packages/eslint-plugin"; +"package/eslint-plugin.md": { + id: "package/eslint-plugin.md"; + slug: "package/eslint-plugin"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/form-web.md": { - id: "packages/form-web.md"; - slug: "packages/form-web"; +"package/form-web.md": { + id: "package/form-web.md"; + slug: "package/form-web"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/form.md": { - id: "packages/form.md"; - slug: "packages/form"; +"package/form.md": { + id: "package/form.md"; + slug: "package/form"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/framework.md": { - id: "packages/framework.md"; - slug: "packages/framework"; +"package/framework.md": { + id: "package/framework.md"; + slug: "package/framework"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/hooks.md": { - id: "packages/hooks.md"; - slug: "packages/hooks"; +"package/hooks.md": { + id: "package/hooks.md"; + slug: "package/hooks"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/jsx.md": { - id: "packages/jsx.md"; - slug: "packages/jsx"; +"package/jsx.md": { + id: "package/jsx.md"; + slug: "package/jsx"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/lens.md": { - id: "packages/lens.md"; - slug: "packages/lens"; +"package/lens.md": { + id: "package/lens.md"; + slug: "package/lens"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/logger.md": { - id: "packages/logger.md"; - slug: "packages/logger"; +"package/logger.md": { + id: "package/logger.md"; + slug: "package/logger"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -<<<<<<< HEAD -"package/npm-solid-js.md": { - id: "package/npm-solid-js.md"; - slug: "package/npm-solid-js"; +"package/npm-cookie-baker.md": { + id: "package/npm-cookie-baker.md"; + slug: "package/npm-cookie-baker"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"package/persist-web-storage.md": { - id: "package/persist-web-storage.md"; - slug: "package/persist-web-storage"; -======= -"packages/npm-cookie-baker.md": { - id: "packages/npm-cookie-baker.md"; - slug: "packages/npm-cookie-baker"; ->>>>>>> 2505f77 (docs: fix build after re-arrange) +"package/npm-history.md": { + id: "package/npm-history.md"; + slug: "package/npm-history"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/npm-history.md": { - id: "packages/npm-history.md"; - slug: "packages/npm-history"; +"package/npm-react.md": { + id: "package/npm-react.md"; + slug: "package/npm-react"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/npm-react.md": { - id: "packages/npm-react.md"; - slug: "packages/npm-react"; +"package/npm-solid-js.md": { + id: "package/npm-solid-js.md"; + slug: "package/npm-solid-js"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/npm-svelte.md": { - id: "packages/npm-svelte.md"; - slug: "packages/npm-svelte"; +"package/npm-svelte.md": { + id: "package/npm-svelte.md"; + slug: "package/npm-svelte"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/persist-web-storage.md": { - id: "packages/persist-web-storage.md"; - slug: "packages/persist-web-storage"; +"package/persist-web-storage.md": { + id: "package/persist-web-storage.md"; + slug: "package/persist-web-storage"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/persist.md": { - id: "packages/persist.md"; - slug: "packages/persist"; +"package/persist.md": { + id: "package/persist.md"; + slug: "package/persist"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/primitives.md": { - id: "packages/primitives.md"; - slug: "packages/primitives"; +"package/primitives.md": { + id: "package/primitives.md"; + slug: "package/primitives"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/testing.md": { - id: "packages/testing.md"; - slug: "packages/testing"; +"package/testing.md": { + id: "package/testing.md"; + slug: "package/testing"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/timer.md": { - id: "packages/timer.md"; - slug: "packages/timer"; +"package/timer.md": { + id: "package/timer.md"; + slug: "package/timer"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/undo.md": { - id: "packages/undo.md"; - slug: "packages/undo"; +"package/undo.md": { + id: "package/undo.md"; + slug: "package/undo"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/url.md": { - id: "packages/url.md"; - slug: "packages/url"; +"package/url.md": { + id: "package/url.md"; + slug: "package/url"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/utils.md": { - id: "packages/utils.md"; - slug: "packages/utils"; +"package/utils.md": { + id: "package/utils.md"; + slug: "package/utils"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"packages/web.md": { - id: "packages/web.md"; - slug: "packages/web"; +"package/web.md": { + id: "package/web.md"; + slug: "package/web"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> @@ -539,13 +530,6 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".md"] }; -"package/web.md": { - id: "package/web.md"; - slug: "package/web"; - body: string; - collection: "docs"; - data: InferEntrySchema<"docs"> -} & { render(): Render[".md"] }; "repl.mdx": { id: "repl.mdx"; slug: "repl"; diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 2f3dcfe34..d7ff401a6 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -47,7 +47,7 @@ export default defineConfig({ { label: 'Packages', autogenerate: { - directory: 'packages', + directory: 'package', }, }, { diff --git a/docs/src/content/docs/adapter/npm-history.md b/docs/src/content/docs/adapter/npm-history.md new file mode 100644 index 000000000..41337f577 --- /dev/null +++ b/docs/src/content/docs/adapter/npm-history.md @@ -0,0 +1,39 @@ +--- +title: npm-history +description: Reatom for npm-history +--- + + + +## Installation + +```sh +npm i @reatom/npm-history +``` + +## Usage + +You should setup `historyAtom` in the root of your app, before other dependent atoms will touch the ctx. + +```ts +import { historyAtom } from '@reatom/npm-history' +import { createBrowserHistory } from 'history' + +historyAtom(ctx, createBrowserHistory()) +``` + +```ts +import { History, Location, To, Blocker } from 'history' + +export interface HistoryAtom extends AtomMut { + back: Action<[]> + block: Action<[blocker: Blocker], () => void> + forward: Action<[]> + go: Action<[delta: number]> + location: Atom + push: Action<[to: To, state?: any]> + replace: Action<[to: To, state?: any]> +} + +export const historyAtom: HistoryAtom +``` diff --git a/docs/src/content/docs/adapter/npm-react.md b/docs/src/content/docs/adapter/npm-react.md new file mode 100644 index 000000000..c9e7edca0 --- /dev/null +++ b/docs/src/content/docs/adapter/npm-react.md @@ -0,0 +1,361 @@ +--- +title: npm-react +description: Reatom adapter for React +--- + + + +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 core docs](/core) first for production usage. + +## Use atom + +`useAtom` is your main 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) => + inputAtom(ctx, event.currentTarget.value), + 'onChange', +) + +export const Greeting = () => { + const [input] = useAtom(inputAtom) + const [greeting] = useAtom(greetingAtom) + const handleChange = useAction(onChange) + + return ( + <> + + {greeting} + + ) +} +``` + +In the app root: + +```js +import { createCtx } from '@reatom/core' +import { reatomContext } from '@reatom/npm-react' + +const ctx = createCtx() + +export const App = () => ( + +
+ +) +``` + +We recommend to setup [logger](/package/logger) here. + +## Use atom selector + +It is possible to paste 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 +} +``` + +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 +} +``` + +### Advanced usage + +```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 ( + <> + + {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 ( + <> + + + {/* this will not rerender by filters or data changes */} + + +) +``` + +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 ( + <> + + numbers: {numbers} + +) + +// onChange "q" - no rerender +// onChange "qw" - no rerender +// onChange "qw1" - rerender +// onChange "qw1e" - no rerender +``` + +## 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 [updates hook](/guides/lifecycle) and your callback will call during transaction, so you need to schedule an effects, but could mutate an atoms without batching (as it 'on' already). 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/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 ( +
+ + ..... + + ) +} +``` + +## 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](/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 ( +
+
    + {list.map((el) => ( +
  • ...
  • + ))} +
+
+ + {page} + +
+ ) +} +``` + +## 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 ( + + + + ) +} +``` + +## 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()) +``` diff --git a/docs/src/content/docs/adapter/npm-svelte.md b/docs/src/content/docs/adapter/npm-svelte.md new file mode 100644 index 000000000..336d72b8b --- /dev/null +++ b/docs/src/content/docs/adapter/npm-svelte.md @@ -0,0 +1,89 @@ +--- +title: npm-svelte +description: Reatom adapter for Svelte +--- + + + +## Installation + +```sh +npm i @reatom/npm-svelte +``` + +## Usage + +First of all, you need to setup `ctx` for svelte context in your root component. + +```ts +import { setupCtx } from '@reatom/npm-svelte' + +setupCtx() +``` + +We recommend to add logger for dev environment, from [@reatom/logger](/package/logger) directly, or from [@reatom/framework](/package/framework). + +```ts +import { createCtx, connectLogger } from '@reatom/framework' +import { setupCtx } from '@reatom/npm-svelte' +import { dev } from '$app/environment' // SvelteKit + +const ctx = createCtx() +if (dev) connectLogger(ctx) +setupCtx(ctx) +``` + +Than you can bind `subscribe` and `set` (for mutable atoms) to your atoms by `withSvelte` and use it as a store. + +> **IMPORTANT**, use `withSvelte` only in component `script` tag, as it reads the context. + +```svelte + + + +``` + +Of course, you could describe atoms as a [separate module](/guides/architecture) and bind actions with the same `withSvelte(anAction).set`. + +[repl](https://svelte.dev/repl/416d3e07447440729416e77e45071b87?version=3.55.0). + +```svelte + + + +``` + +```ts +// model.ts +import { atom, action } from '@reatom/core' + +export const countAtom = atom(0, 'countAtom') +export const timesAtom = atom( + (ctx) => (ctx.spy(countAtom) === 1 ? 'time' : 'times'), + 'timesAtom', +) +export const increment = action( + (ctx) => countAtom(ctx, (s) => ++s), + 'increment', +) +``` + +### Data fetching and Svelte + +https://svelte.dev/repl/0613e23e6aa74246afad6d726d6c5a33?version=3.55.0 diff --git a/docs/src/content/docs/core.md b/docs/src/content/docs/core.md index 5f28222dc..3ce696a70 100644 --- a/docs/src/content/docs/core.md +++ b/docs/src/content/docs/core.md @@ -2,8 +2,10 @@ title: core description: The ultimate state manager --- - -Tiny, efficient, featured and extensible core to handle reactivity right. The ultimate state manager. Build anything, from a small widget to a huge application. + + + +Tiny, efficient, featured, and extensible core to handle reactivity right. The ultimate state manager. Build anything, from a small widget to a huge application. > included in [@reatom/framework](/package/framework) @@ -13,7 +15,7 @@ The raw API description is [below](#api). ## About -Reatom allows you to describe both simple and complex logic using three main components: **atoms** for data reference, **actions** for logic processing, and **context** (`ctx`) for system isolation. This core is a perfect solution for building your own high-order library or the whole framework, all the packages stay on top of that. +Reatom allows you to describe both simple and complex logic using three main components: **atoms** for data reference, **actions** for logic processing, and **context** (`ctx`) for system isolation. This core is a perfect solution for building your own high-order library or an entire framework, with all the packages built on top of it. Reatom is inspired by the React and Redux architectures. All processed data should be [immutable](https://developer.mozilla.org/en-US/docs/Glossary/Immutable), computations should be pure, and all side effects should be scheduled for a separate effects queue using `ctx.schedule(callback)`. Only consistent data transactions should be applied. All prerequisites can be checked in this article: [What is a state manager](/general/what-is-state-manager). @@ -25,8 +27,8 @@ npm i @reatom/core ## Usage -Let's describe a simple example of a search input with a tip and a list of goods. This code is written in TypeScript, but you could also use JavaScript; a lot of types are inferred automatically. -Take your attention to the comments; they will help you to understand the core concepts. +Let's describe a simple example of a search input with a tip and a list of goods. This code is written in TypeScript, but you can also use JavaScript; a lot of types are inferred automatically. +Pay your attention to the comments; they will help you to understand the core concepts. ```ts // ~/ctx.ts @@ -37,11 +39,11 @@ import { createCtx } from '@reatom/core' export const ctx = createCtx() ``` -All atoms and actions accept `ctx` as their first argument to match and process all data inside it. It helps you a lot in many things: testing, debugging, SSR, effects chains management and logging. It is the **most powerful** feature of Reatom, and your best friend in any complex case. And it only requires three extra letters for each function call - super tiny! +All atoms and actions accept `ctx` as their first argument to match and process all data inside it. It assists you greatly in many areas: testing, debugging, SSR, effects chain management, and logging. It is the **most powerful** feature of Reatom and is indispensable in complex scenarios. And it only requires three extra letters for each function call - super efficient! As the entire data processing flows through the context, you can easily inspect it: `ctx.subscribe(logs => console.log(logs))` or connect a separate [logger](/package/logger) to view all changes in your app with proper formatting. -Now, let's describe a logic. +Now, let's outline some logic. ```ts // ~/features/search/model.ts @@ -119,12 +121,12 @@ document.getElementById('search-input').addEventListener('input', (event) => { }) ``` -> Do you want to see next [the docs for React adapter](/adapter/npm-react)? +> Do you want to see [the docs for React adapter](/adapter/npm-react) next? ### Action handling (advanced) -It is better to stay atoms stupid and handle all logic inside actions. But sometimes you need to turn the direction of your code coupling and make atoms depend on an action. And you can do it! +It is better to keep atoms stupid and handle all logic inside actions. But sometimes you need to turn the direction of your code coupling and make atoms depend on an action. And you can do it! An action is an atom with a temporal state, which is an array of all passed payloads. This state is cleared after the transaction ends; if you try to `get` or `spy` an action which wasn't called, you will receive an empty array. But if the action was called, the array will contain some elements. @@ -224,8 +226,7 @@ export const usCurrencyAtom = atom(0) export const euCurrencyAtom = atom(0) export const currencyValueAtom = atom((ctx) => { const currency = ctx.spy(currencyAtom) - // use `if` or `switch` if you want - const valueAtom = { us: usCurrencyAtom, eu: euCurrencyAtom }[currency] + const valueAtom = currency === 'us' ? usCurrencyAtom : euCurrencyAtom return ctx.spy(valueAtom) }) ``` @@ -283,9 +284,9 @@ const countAtom = atom(0).pipe( const countAtom = withInit(() => localStorage.getItem('COUNT') ?? 0)(atom(0)) ``` -> `withInit` allows you to configure the init state of the atom reading, which is more pridictable and safe for a testing sometimes. It is a part of [reatom/hooks](/package/hooks#withinit) package. +> `withInit` allows you to configure the initial state of the atom reading, which is sometimes more predictable and safer for testing. -Operator `with` prefix mean that the target atom will be changed somehow and the returned reference will the same. [reatom/async](/package/async) uses operators a lot to configure the behavior of the effect by composition, which is good for tree-shaking. Check naming conventions and more examples in [this guild](/guides/naming#operator-prefix) +Operator `with` prefix mean that the target atom will be changed somehow and the returned reference will the same. [reatom/async](/package/async) uses operators a lot to configure the behavior of the effect by composition, which is good for tree-shaking. Check naming conventions and more examples in [this guide](/guides/naming#operator-prefix) Btw, actions has `pipe` too! @@ -305,7 +306,7 @@ searchAtom.onChange(fetchSearchSuggestion) `onChange` returns an unsubscribe function which you should use if you are adding a hook dynamically to a global atom. -The important difference of a hook from a subscription is that it does not activate the connections. +The key difference between a hook and a subscription is that the hook does not activate the connections. ```ts const searchAtom = atom('', 'searchAtom') @@ -317,7 +318,7 @@ const filteredSearchAtom = atom((ctx, state = '') => { const search = ctx.spy(searchAtom) return search.length >= 3 ? search : state }, 'filteredSearchAtom') -// the hook will not called if the atom have no subscription, as it lazy. +// the hook will not be called if the atom has no subscription, as it's lazy. filteredSearchAtom.onChange(fetchSearchSuggestion) ``` @@ -382,9 +383,9 @@ doSome.onCall((ctx, payload, params) => { ### `ctx` API -`CTX` is the main shell for a state of all atoms, all user and meta data leaves here. Each atom and action produces an immutable version of the context and it should not be mutated! +`ctx` is the main shell that holds the state for all atoms, and where all user and metadata reside. Each atom and action produces an immutable version of the context and it should not be mutated! -One more rule, which you probably won't need, but we should still mention it: don't run one context inside another, such as `ctx1.get(() => ctx2.get(anAtom))` - this will throw an error. +An important rule to note, even if you might not need it, is: don't run one context inside another, such as ctx1.get(() => ctx2.get(anAtom)). Doing so will throw an error. #### `ctx.get` atom API @@ -424,23 +425,23 @@ const fetchData = action((ctx) => { }) ``` -The unique feature of Reatom and the schedule specially is ability to define the target queue. The second argument of `schedule` is a priority number: +A unique feature of Reatom, especially in scheduling, is ability to define the target queue. The second argument of `schedule` is a priority number: -- `-1` - rollback queue, useful when you need to do a side-effect during pure computations. Check example [below](#ctxschedule-rollback-api). +- `-1` - rollback queue, useful when you need to do a side-effect during pure computations. Check the example [below](#ctxschedule-rollback-api). - `0` - computations queue, schedule **pure** computation, which will call right after current batch. -- `1` - the **default** near effect queue, used to schedule regular effects. This effects calling could be redefined (delayed) in `callNearEffect` option of `createCtx` -- `2` - lates effect queue, used to schedule subscribers. This effects calling could be redefined (delayed) in `callLateEffect` option of `createCtx`. +- `1` - the **default** near effect queue, used to schedule regular effects. The calling of these effects can be redefined (or delayed) using the `callNearEffect` option of `createCtx`. +- `2` - lates effect queue, used to schedule subscribers. The calling of these effects can be redefined (or delayed) using the `callLateEffect` option of `createCtx`. -> Read more in [lifecycle guild](/guides/lifecycle). +> Read more in the [lifecycle guild](/guides/lifecycle). #### `ctx.schedule` rollback API -Sometimes you want to do a side-effect during clean calculations or need to store some artifact of an effect and store it. To make it clean, you should describe a rollback (cleanup) function for the case of an unexpected error by passing `-1` as the second argument of `ctx.schedule`. Check out this example with a debounced action: +Sometimes, you may want to perform a side-effect during clean calculations or need to store an artifact of an effect. To make it clean, you should describe a rollback (cleanup) function for the case of an unexpected error by passing `-1` as the second argument of `ctx.schedule`. Check out this example with a debounced action: ```ts const timeoutIdAtom = atom(-1) -// `timeoutIdAtom` update is in a schedule cause an extra transaction - not handy +// `timeoutIdAtom` update in a schedule causes an extra transaction, which is not handy. export const doSome = action((ctx) => { const timeoutId = ctx.get(timeoutIdAtom) @@ -450,7 +451,7 @@ export const doSome = action((ctx) => { timeoutIdAtom(ctx, newTimeoutId) }) }) -// `timeoutIdAtom` update is during transaction more obvious +// updating `timeoutIdAtom` during a transaction is more obvious. export const doSome = action((ctx) => { const timeoutId = ctx.get(timeoutIdAtom) ctx.schedule(() => clearTimeout(timeoutId)) diff --git a/docs/src/content/docs/examples.md b/docs/src/content/docs/examples.md index 995e58466..a83190736 100644 --- a/docs/src/content/docs/examples.md +++ b/docs/src/content/docs/examples.md @@ -25,7 +25,7 @@ https://github.com/artalar/RTK-entities-basic-example/pull/1 ### Search component -This example is close to real life and shows the complexity of interactive UI. It uses [async package](https://www.reatom.dev/package/async) to handle classic search edge cases and made perfect UX. +This example is close to real life and shows the complexity of interactive UI. It uses [async package](/package/async) to handle classic search edge cases and made perfect UX. https://codesandbox.io/s/reatom-react-search-component-l4pe8q?file=/src/App.tsx diff --git a/docs/src/content/docs/getting-started/debugging.md b/docs/src/content/docs/getting-started/debugging.md index 1104b19f2..918852b1d 100644 --- a/docs/src/content/docs/getting-started/debugging.md +++ b/docs/src/content/docs/getting-started/debugging.md @@ -66,32 +66,4 @@ Reatom 1 transaction - cause - describe why this update happens, why trigger it - history - atom values that was before update -Let's look at more complex example ([live](https://stackblitz.com/edit/github-dagxch-1aynbc?file=src%2FApp.tsx)) - -```tsx -const productAtom = atom(null) - -const loadProduct = action(async (ctx) => { - const res = await fetch('https://dummyjson.com/products/3') - const product = await res.json() - productAtom(ctx, product) -}) - -export const App = () => { - const [product] = useAtom(productAtom) - const onClick = useAction(loadProduct) - return ( -
- { product ? {product} : } -
- ) -} - -const ProductCard = ({ children: p }) => { - return
- -

{p.title}

-

{p.description}

-
-} -``` +More complex example you can find in [@reatom/logger] package documentation diff --git a/docs/src/content/docs/getting-started/setup.md b/docs/src/content/docs/getting-started/setup.md index c98f63b1b..fb6938338 100644 --- a/docs/src/content/docs/getting-started/setup.md +++ b/docs/src/content/docs/getting-started/setup.md @@ -94,7 +94,7 @@ Example of customizing rules: } ``` -More examples you can found in [package documentation](https://www.npmjs.com/package/@reatom/eslint-plugin) +More examples you can found in [@reatom/eslint-plugin package documentation](https://www.reatom.dev/package/eslint-plugin/) ### With React @@ -208,7 +208,7 @@ const Greeting = () => { ``` -This is very basic functionality of reatom-react bindings, see more in [package documentation](https://www.npmjs.com/package/@reatom/npm-react) +This is very basic functionality of reatom-react bindings, see more in [@reatom/npm-react package documentation](/package/npm-react/)