Skip to content

Commit

Permalink
docs: sync
Browse files Browse the repository at this point in the history
  • Loading branch information
artalar committed Feb 27, 2025
1 parent 389cce0 commit 6f150a0
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
7 changes: 3 additions & 4 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](/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 @@ -221,7 +221,7 @@ export const Search = () => {
}
```

The logic definition consists of only about 15 lines of code and is entirely independent from 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 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 +261,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,7 +299,6 @@ 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
114 changes: 113 additions & 1 deletion docs/src/content/docs/package/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export const fetchList = reatomAsync(
'fetchList',
).pipe(withCache(), withDataAtom())

export const updateList = reatomAction(async () => {
export const updateList = reatomAsync(async () => {
/* */
}, 'updateList')
updateList.onFulfill.onCall(fetchList.cacheAtom.reset)
Expand Down Expand Up @@ -554,6 +554,118 @@ export const fetchList = reatomAsync(
).pipe(withDataAtom([]), withCache({ withPersist }))
```

### Sharing cache

In this example, we'll explore how to create an asynchronous model with a shared cache that can be used across different components. The key feature is that some components can request the one data with one parameters, and another component can request a different data with different parameters, but they share the same cache.

Imagine you have an application where multiple components request user data. Most components display data for the current user, but there's a special component that needs to show data for a different user.

Let's dive into solution:

```typescript
import {
atom,
createCtx,
reatomResource,
withDataAtom,
withCache,
createMemStorage,
reatomPersist,
} from '@reatom/framework'
import { withLocalStorage } from '@reatom/persist-web-storage'

// API request simulation
const fetchUserData = async (userId: number): Promise<string> => {
await new Promise((resolve) => setTimeout(resolve, 500)) // delay simulation
return `User data for ID: ${userId}`
}

// Create shared storage for caching
const userModelStorage = createMemStorage({ name: 'userData' })
const withUserDataPersist = reatomPersist(userModelStorage)

// Factory function to create model instances with shared cache
const reatomUser = (initialUserId: number, name: string) => {
// Parameter atom that can be changed
const userIdAtom = atom(initialUserId, `${name}.userIdAtom`)

// Resource that fetches data based on the parameter
const userResource = reatomResource(async (ctx) => {
const userId = ctx.spy(userIdAtom)
return await fetchUserData(userId)
}, `${name}.userResource`).pipe(
withDataAtom(''),
withCache({
swr: false,
withPersist: () => withUserDataPersist('userData'), // Share persistence between instances
}),
)

return {
idAtom: userIdAtom,
resource: userResource,
}
}

// Current user model
const ownerModel = reatomUser(0, 'ownerModel')
ownerModel.idAtom.pipe(withLocalStorage('user-id'))

// Custom user model
const customUserModel = reatomUser(0, 'customUserModel')

// View layer
const OwnerLoading = reatomComponent(
({ ctx }) => (ctx.spy(ownerModel.isLoadingAtom) ? <p>Loading...</p> : null),
'OwnerLoading',
)
export const OwnerData = reatomComponent(({ ctx }) => {
return (
<div>
<OwnerLoading />
<p>Your data: {ctx.spy(ownerModel.dataAtom)}</p>
</div>
)
}, 'OwnerData')

export const CustomUserCard = reatomComponent(({ ctx }) => {
return (
<div>
<h2>User inspector</h2>
<input
type="number"
value={ctx.spy(customUserModel.idAtom)}
onChange={(e) => customUserModel.idAtom.set(e.target.valueAsNumber)}
/>
<p>User data: {ctx.spy(ownerModel.dataAtom)}</p>
</div>
)
}, 'CustomUserCard')
```

#### How It Works

1. **Factory Pattern**: The `reatomUser` factory function allows us to create multiple model instances without code duplication, each tracking different user IDs.
2. **Shared Cache**: We create a single `userModelStorage` that will be used for caching all user data requests. For example, CustomUserCard will not fetch new data, if user will put their own ID, as it is already cached by OwnerData.
3. **In memory cache**: Using createMemStorage didn't persist (stringify) used cache, so you can easily use [atomization](/recipes/atomization/) without extra `toSnapshot`, `fromSnapshot`.

This pattern is particularly useful for applications that need to display variations of the same data type in different contexts, while still maintaining efficient resource usage.

Another example of using this shared cache, is mimic `useRequest` logic from [tanstack-query](https://tanstack.com/query/) and create a new instance of the query with `useMemo`. You can do it safely in many components, read and mutate `dataAtom` individually, only cache will be shared.

```ts
export const UserCard = reatomComponent<{ id: number }>(({ ctx, id }) => {
const model = useMemo(() => reatomUser(id), [id])
return (
<div>
<h2>User #{id}</h2>
{ctx.spy(model.isLoadingAtom) && <p>Loading...</p>}
<p>{ctx.spy(model.dataAtom)}</p>
</div>
)
}, 'UserCard')
```

## withRetry

Adds `retry` action and `paramsAtom` to store last params of the effect call.
Expand Down

0 comments on commit 6f150a0

Please sign in to comment.