Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add info about computed atoms edge case #916

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)


## 🚀 Project Structure

Inside of your Astro project, you'll see the following folders and files:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/blog/what-is-state-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The last condition is very important - computational resources are limited, so w

You can talk about the state as data related by some meaning, although often we are talking about some specific cache. For example, traffic light data contains information about three light bulbs, their colors and which one is on. Semantics follows from the subject area and represents the meaning of the data: only one light bulb can be turned on at a time, and the order of their switching is strictly regulated, this information is described not by the data structure, but by the code, therefore less explicit, although no less important.

The traffic light example deduced an important distinguishing feature of state as a phenomenon, is the need for data consistency: we cannot turn on one light without turning off the other, otherwise we would get erroneous data with their unpredictable impact on the user. The property of a state to be always consistent, i.e. to contain non-contradictory data, is called [atomicity](https://en.wikipedia.org/wiki/Atomicity_(database_systems)) in database theory.
The traffic light example deduced an important distinguishing feature of state as a phenomenon, is the need for data consistency: we cannot turn on one light without turning off the other, otherwise we would get erroneous data with their unpredictable impact on the user. The property of a state to be always consistent, i.e. to contain non-contradictory data, is called [atomicity](<https://en.wikipedia.org/wiki/Atomicity_(database_systems)>) in database theory.

> [Here is the test of atomicity for a few state managers](https://github.com/artalar/state-management-specification/blob/master/src/index.test.js). Btw, React.js throws all your app away from screen if uncaught error occurs in render function, there is no way to get inconsistent state during render.

Expand Down
16 changes: 4 additions & 12 deletions docs/src/content/docs/compat/core-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ before
import { declareAction, declareAtom, map, combine } from '@reatom/core-v1'

const add = declareAction()
const n1Atom = declareAtom(0, (on) => [
on(add, (state, value) => state + value),
])
const n2Atom = declareAtom(0, (on) => [
on(add, (state, value) => state + value),
])
const n1Atom = declareAtom(0, (on) => [on(add, (state, value) => state + value)])
const n2Atom = declareAtom(0, (on) => [on(add, (state, value) => state + value)])
const sumAtom = map(combine([n1Atom, n2Atom]), ([n1, n2]) => n1 + n2)
const rootAtom = combine({ sumAtom })
```
Expand All @@ -34,12 +30,8 @@ import { declareAction, declareAtom, combine, v3toV1 } from '@reatom/core-v1'
import { atom } from '@reatom/core'

const add = declareAction()
const n1Atom = declareAtom(0, (on) => [
on(add, (state, value) => state + value),
])
const n2Atom = declareAtom(0, (on) => [
on(add, (state, value) => state + value),
])
const n1Atom = declareAtom(0, (on) => [on(add, (state, value) => state + value)])
const n2Atom = declareAtom(0, (on) => [on(add, (state, value) => state + value)])
const sumAtom = atom((ctx) => ctx.spy(n1Atom.v3atom) + ctx.spy(n2Atom.v3atom))
const rootAtom = combine({ sumAtom: v3toV1(sumAtom) })
```
Expand Down
52 changes: 18 additions & 34 deletions docs/src/content/docs/compat/core-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,10 @@ import { createAtom } from '@reatom/core-v2'
type TimerCtx = { intervalId?: number | NodeJS.Timer | any }

/** Timer update interval */
export const intervalAtom = createAtom(
{ setSeconds: (seconds: number) => seconds },
({ onAction }, state = 1000) => {
onAction(`setSeconds`, (seconds) => (state = seconds * 1000))
return state
},
)
export const intervalAtom = createAtom({ setSeconds: (seconds: number) => seconds }, ({ onAction }, state = 1000) => {
onAction(`setSeconds`, (seconds) => (state = seconds * 1000))
return state
})

export const timerAtom = createAtom(
{
Expand All @@ -239,10 +236,7 @@ export const timerAtom = createAtom(

if (remains <= interval) {
clearInterval(ctx.intervalId)
ctx.intervalId = setTimeout(
() => dispatch(create(`_update`, 0)),
remains,
)
ctx.intervalId = setTimeout(() => dispatch(create(`_update`, 0)), remains)
}

dispatch(create(`_update`, remains))
Expand Down Expand Up @@ -318,10 +312,7 @@ But a better way is use the `createEnumAtom`.
```ts
import { createEnumAtom } from '@reatom/core-v2/primitives'

const githubRepoSortFilterAtom = createEnumAtom(
['full_name', 'created', 'updated', 'pushed'],
{ format: 'snake_case' },
)
const githubRepoSortFilterAtom = createEnumAtom(['full_name', 'created', 'updated', 'pushed'], { format: 'snake_case' })

console.log(sortFilterAtom.getState())
// -> 'full_name'
Expand Down Expand Up @@ -462,11 +453,7 @@ const formAtom = createAtom(
// you should't call `track.get` async
// (scheduled callback calls async after all atoms)
// (use `email` and `password` variables instead)
track.create(
'_fetch',
track.get('emailAtom'),
track.get('passwordAtom'),
),
track.create('_fetch', track.get('emailAtom'), track.get('passwordAtom')),
),
)
})
Expand Down Expand Up @@ -540,21 +527,18 @@ const counterAtom = createAtom({ inc: () => {} }, ({ onAction }, state = 0) => {
Important note. Feel free to mutate **variable**, not a value. Reducer functions should not mutate any input values.

```ts
const counterAtom = createAtom(
{ inc: () => {} },
({ onAction }, state = { count: 0 }) => {
// WRONG
onAction('inc', () => {
state.count++
})
// Right
onAction('inc', () => {
state = { count: state.count + 1 }
})
const counterAtom = createAtom({ inc: () => {} }, ({ onAction }, state = { count: 0 }) => {
// WRONG
onAction('inc', () => {
state.count++
})
// Right
onAction('inc', () => {
state = { count: state.count + 1 }
})

return state
},
)
return state
})
```

### How to handle one action in a few atoms?
Expand Down
2 changes: 0 additions & 2 deletions docs/src/content/docs/compat/react-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ description: Reatom for react-v1
<!-- DO NOT EDIT THIS FILE -->
<!-- CHECK "packages/*/README.md" -->



This is compatible package which allow you to use `@reatom/core-v1` with react. All docs is [here](/package/npm-react/).

## Setup batching for old React
Expand Down
10 changes: 2 additions & 8 deletions docs/src/content/docs/compat/react-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ const [data] = useAtom(dataAtom)
#### Depended value by selector

```ts
const [propAtom] = useMemo(
() => createAtom({ dataAtom }, ({ get }) => get('dataAtom')[props.id]),
[props.id],
)
const [propAtom] = useMemo(() => createAtom({ dataAtom }, ({ get }) => get('dataAtom')[props.id]), [props.id])
const [propValue] = useAtom(propAtom)
```

Expand All @@ -82,10 +79,7 @@ const handleUpdateData = useAction(dataAtom.update)
#### Prepare payload for dispatch

```ts
const handleUpdateData = useAction(
(value) => dataAtom.update({ id: props.id, value }),
[props.id],
)
const handleUpdateData = useAction((value) => dataAtom.update({ id: props.id, value }), [props.id])
```

#### Conditional dispatch
Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/getting-started/learning.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const cAtom = atom((ctx) => ctx.spy(aAtom) + ctx.spy(bAtom), 'cAtom')

Computed atoms should be pure functions to ensure the correct order of all computations.

> Note: If a computed atom doesn't spy any other atoms, it will update whenever any atom in the context changes.

### Read Atom

To read the value of an atom, you need to use the previously created context.
Expand Down Expand Up @@ -206,9 +208,7 @@ export const todoAtom = atom(null)
export const isLoadingAtom = atom(false)

export const fetchTodo = action(async (ctx) => {
const response = await ctx.schedule(() =>
fetch('https://jsonplaceholder.typicode.com/todos/1'),
)
const response = await ctx.schedule(() => fetch('https://jsonplaceholder.typicode.com/todos/1'))
return await response.json()
})

Expand Down
12 changes: 5 additions & 7 deletions docs/src/content/docs/getting-started/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ The base template project includes Vite, TypeScript, React and Reatom ecosystem.
You can check in out [here](https://github.com/artalar/reatom-react-ts)

You can also try it online:
- [codesandbox](https://codesandbox.io/p/sandbox/github/artalar/reatom-react-ts/tree/main)
- [stackblitz](https://githubblitz.com/artalar/reatom-react-ts)
- [gitpod](https://gitpod.io/#https://github.com/artalar/reatom-react-ts)

- [codesandbox](https://codesandbox.io/p/sandbox/github/artalar/reatom-react-ts/tree/main)
- [stackblitz](https://githubblitz.com/artalar/reatom-react-ts)
- [gitpod](https://gitpod.io/#https://github.com/artalar/reatom-react-ts)

To setup it in your machine you can use the [degit](https://github.com/Rich-Harris/degit) package.

Expand Down Expand Up @@ -90,10 +91,7 @@ const nameAtom = atom('Joe')
const Greeting = () => {
const t = useTranslation()
const [name, setName] = useAtom(nameAtom)
const [greeting] = useAtom(
(ctx) => `${t('common:GREETING')} ${ctx.spy(nameAtom)}!`,
[t],
)
const [greeting] = useAtom((ctx) => `${t('common:GREETING')} ${ctx.spy(nameAtom)}!`, [t])

return (
<>
Expand Down
14 changes: 7 additions & 7 deletions docs/src/content/docs/getting-started/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ In the next example, we have an async API.
import { action, atom } from '@reatom/core'

export const todoAtom = atom(null)
export const isLoadingAtom = atom(false);
export const isLoadingAtom = atom(false)

export const fetchTodo = action(async (ctx) => {
const response = await ctx.schedule(() => fetch('https://jsonplaceholder.typicode.com/todos/1'))
return await response.json();
return await response.json()
})

export const loadTodo = action(async (ctx) => {
try {
isLoadingAtom(ctx, true)
const data = await ctx.schedule((ctx) => fetchTodo(ctx))
todoAtom(ctx, data);
todoAtom(ctx, data)
} catch (e) {
console.error(e)
} finally {
Expand All @@ -80,13 +80,13 @@ export const loadTodo = action(async (ctx) => {
Let's test it without calling the real api

```js
import { expect, test } from 'vitest';
import { createTestCtx } from '@reatom/testing';
import { loadTodo, fetchTodo, todoAtom } from './main';
import { expect, test } from 'vitest'
import { createTestCtx } from '@reatom/testing'
import { loadTodo, fetchTodo, todoAtom } from './main'

test('Test loadData atom', async () => {
const ctx = createTestCtx()
const track = ctx.subscribeTrack(todoAtom)
const track = ctx.subscribeTrack(todoAtom)

// Mock action with call
ctx.mockAction(fetchTodo, (ctx) => Promise.resolve([{ id: 'foo' }]))
Expand Down
17 changes: 6 additions & 11 deletions docs/src/content/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ description: Reatom - tiny and powerful reactive system with immutable nature
- **smallest bundle** size: [2 KB](https://bundlejs.com/?q=%40reatom%2Fcore) gzipped
<small>With the power of base primitives, the whole ecosystem with <strong>A&nbsp;LOT</strong> of enterprise-level helpers takes only [~15KB](https://bundlejs.com/?q=%40reatom%2Fframework%2C%40reatom%2Fnpm-react%2C%40reatom%2Fpersist-web-storage%2C%40reatom%2Fundo%2C%40reatom%2Fform-web&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22use-sync-external-store%22%5D%7D%7D). Insane!</small>
- **the best TypeScript** experience
<small>[Type inference](/recipes/typescript/) is one of the main priorities for Reatom.</small>
<small>[Type inference](/recipes/typescript/) is one of the main priorities for Reatom.</small>

[The core package](/core) includes most of these features and, due to its minimal overhead, can be used in any project, from small libraries to large applications.

Expand Down Expand Up @@ -176,9 +176,7 @@ const fetchIssues = reatomAsync(async (ctx, query: string) => {
withRetry({
onReject(ctx, error: any, retries) {
// return delay in ms or -1 to prevent retries
return error?.message.includes('rate limit')
? 100 * Math.min(500, retries ** 2)
: -1
return error?.message.includes('rate limit') ? 100 * Math.min(500, retries ** 2) : -1
},
}),
)
Expand All @@ -205,11 +203,7 @@ export const Search = () => {

return (
<main>
<input
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder="Search"
/>
<input value={search} onChange={(e) => setSearch(e.currentTarget.value)} placeholder="Search" />
{isLoading && 'Loading...'}
<ul>
{issues.map(({ title }, i) => (
Expand All @@ -221,7 +215,7 @@ export const Search = () => {
}
```

The logic definition consists of only about 15 lines of code and is entirely independent from the the view part (React in our case). It makes it easy to test.
The logic definition consists of only about 15 lines of code and is entirely independent from the the view part (React in our case). It makes it easy to test.
Imagine the line count in other libraries!
The most impressive part is that the overhead is [less than 4KB (gzip)](https://bundlejs.com/?q=%28import%29%40reatom%2Fframework%2C%28import%29%40reatom%2Fnpm-react&treeshake=%5B%7B%0A++atom%2CcreateCtx%2ConUpdate%2CreatomAsync%2Csleep%2CwithAbort%2CwithDataAtom%2CwithRetry%2C%7D%5D%2C%5B%7B+useAtom+%7D%5D&share=MYewdgzgLgBBCmBDATsAFgQSiAtjAvDItjgBQBE5ANDOQiulruQJQDcAUKJLAGbxR0ASQgQArvAgEYyJCQwQAnmGClESlTFLAoADxoBHCckUAuOFGQBLMAHMWBAHwwA3hxhEA7oiuwIAG3h4AAdSACYAVgAGdhgAejiYABN4ACMQMRV4dxhuaFcYX3gcKQBfaURvXyJgqwA6fkE0EXFJUiN4ExodXTruSxB-QOR2HNkoMWQwQqhiiE5SmnJG4VEJCFY62uD4UhzPXzQAEWJEJjIAbQBdFip9w4x05ChSFwtkYnhbM1p-dSgALQ2AEHMDkGClW73KBoABKAhMrxyHnA8IAVvAdNo9DROsgQMhzIgwIoaONrJIHG4PDT4olxpNpik-opCtMSjACTAAQBGGDYGDBWQAN3gYFg5KskmRNIZUxgeIJAH46jhJBBELZ4HUbMB-GIUhAKB9ZjB-FYcL5WDLaUqYDyolEYAAqGAAWWIaFVNlI0SiZIRUqkztdYRYNpp5l5nFpixykLuowSMkyMBWzTWkk503gopMcCQqEwJBgYmCSU%2BHHAAFVy59SPQi%2BcaOmWutRlxZJ8AMJ6UijMQIc6kVuZiB1CtQM4kFhAA&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22use-sync-external-store%22%5D%7D%7D). Amazing, right?
On top of that, you’re not limited to network cache. Reatom is powerful and expressive enough to manage any state.
Expand Down Expand Up @@ -261,7 +255,7 @@ While this can be more predictable, it is certainly not optimal.
Effector's hot connections make it unfriendly for factory creation, which prevents the use of [atomization](/recipes/atomization/) patterns necessary for efficient immutability handling.
Additionally, Effector's [bundle size is 2-3 times more significant](https://bundlejs.com/?q=effector&treeshake=%5B%7BcraeteStore%2CcreateEvent%2Ccombine%7D%5D) with [worse performance](https://github.com/artalar/reactive-computed-bench).

[Zustand](https://github.com/pmndrs/zustand), [nanostores](https://github.com/nanostores/nanostores), [xstate](https://xstate.js.org), and [many other](https://gist.github.com/artalar/e5e8a7274dfdfbe9d36c9e5ec22fc650) state managers do not offer the same exceptional combination of type inference, features, bundle size, and performance that Reatom provides.
[Zustand](https://github.com/pmndrs/zustand), [nanostores](https://github.com/nanostores/nanostores), [xstate](https://xstate.js.org), and [many other](https://gist.github.com/artalar/e5e8a7274dfdfbe9d36c9e5ec22fc650) state managers do not offer the same exceptional combination of type inference, features, bundle size, and performance that Reatom provides.

### Why immutability?

Expand Down Expand Up @@ -299,6 +293,7 @@ Also, remember to check out our [atomization guide](/recipes/atomization).
### Limitations

No software is perfect, and Reatom is no exception. Here are some limitations you should be aware of:

- **Immutable Data**: While immutable data structures are great, they can impact performance. In critical situations, think carefully about your data structures. The good news is you [don't have to use normalization](/recipes/atomization).
- **Laziness**: Laziness is less obvious sometimes and might lead to missed updates. However, debugging a missing update is straightforward and often easier than dealing with hot observables' memory leaks and performance issues. We also have [hooks](/package/hooks) for hot linking.
- **Error Handling**: Currently, you can't subscribe to errors from any dependency, but we're working on it. In [reatomAsync](/package/async), passed effects are wrapped in an error handler, allowing you to manage errors, but you need to wrap them explicitly.
Expand Down
Loading