-
-
Notifications
You must be signed in to change notification settings - Fork 622
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into chore/update-dev-dependnecies
- Loading branch information
Showing
2 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
--- | ||
title: Derive | ||
description: This doc describes Derive extension. | ||
nav: 5.01 | ||
keywords: derive,derived,async,zalgo,suspense,promise,react | ||
--- | ||
|
||
#### When is this useful? | ||
|
||
- When local updates to the cache cause micro-suspensions | ||
- When unnecessary recomputation causes performance issues | ||
|
||
Jōtai offers powerful primitives for working with asynchronous data outside of the web framework (e.g. React), and allows the UI and business logic to properly integrate with the data layer. Many data-fetching integrations offer a peek into the client-side cache via atoms. When the cache is not yet populated, the atom has to resolve to a Promise of the value. However, if the value already exists in cache, and we do an optimistic update, then the value can be made available downstream immediately. | ||
|
||
Building data graphs with these dual-natured (sometimes async, sometimes sync) atoms as a base can lead to unnecessary rerenders, stale values and micro-suspensions (in case of React) if not handled with care. | ||
|
||
**jotai-derive** provides a primitive for building asynchronous data graphs that act on values as soon as they are available (either awaiting for them, or acting on them synchronously). | ||
|
||
### Install | ||
|
||
You have to install `jotai-derive` to use this feature. | ||
|
||
``` | ||
npm i jotai-derive | ||
``` | ||
|
||
## derive | ||
|
||
Lets say we have a _dual-natured_ atom, meaning that sometimes we are yet to | ||
know the value (e.g. fetching the data), but other times we update the atom | ||
locally and can know its value immediately (e.g. optimistic updates). | ||
|
||
```ts | ||
// `jotai-derive` is applicable to most data-fetching solutions, not just `jotai-apollo` | ||
import { atomWithQuery } from 'jotai-apollo'; | ||
|
||
// An example of a dual-natured atom | ||
const userAtom: Atom<User | Promise<User>> = | ||
atomWithQuery(...); | ||
|
||
``` | ||
|
||
Below is how a derived atom is usually created. The drawback is that always awaiting (even though the value can be known) | ||
introduces unnecessary deferring and recomputation. | ||
|
||
```ts | ||
// Without `jotai-derive` | ||
|
||
import { atom } from 'jotai' | ||
|
||
// Type is Atom<Promise<string>>, even though | ||
// get(userAtom) does not always return a promise, | ||
// meaning we could compute `uppercaseNameAtom` | ||
// synchronously. | ||
const uppercaseNameAtom = atom(async (get) => { | ||
const user = await get(userAtom) | ||
return user.name.toUppercase() | ||
}) | ||
``` | ||
|
||
Here is how `jotai-derive` is used to create a tighter async data-processing pipeline. | ||
|
||
```ts | ||
// With `jotai-derive` | ||
|
||
import { derive } from 'jotai-derive' | ||
|
||
// Atom<string | Promise<string>> | ||
const uppercaseNameAtom = derive( | ||
[userAtom], // will be awaited only when necessary | ||
(user) => user.name.toUppercase(), | ||
) | ||
``` | ||
|
||
### Multiple async dependencies | ||
|
||
To derive a value from multiple atoms, the array accepts more than one atom. The corresponding values are then passed | ||
into the producer function in the same order. | ||
|
||
```ts | ||
import { derive } from 'jotai-derive' | ||
|
||
// Atom<string | Promise<string>> | ||
const welcomeMessageAtom = derive( | ||
[userAtom, serverNameAtom], | ||
(user, serverName) => `Welcome ${user.name} to ${serverName}!`, | ||
) | ||
``` | ||
|
||
## soon | ||
|
||
For more advances usage, for example **conditional dependencies**, the `soon` and `soonAll` functions | ||
can be used instead (`derive` is a utility wrapper around them). | ||
|
||
### Conditional dependency | ||
|
||
```ts | ||
// pipes allow for cleaner code when using `soon` directly. | ||
import { pipe } from 'remeda'; | ||
import { soon } from 'jotai-derive'; | ||
|
||
// Atom<RestrictedItem | Promise<RestrictedItem>> | ||
const queryAtom = ...; | ||
|
||
// Atom<boolean | Promise<boolean>> | ||
const isAdminAtom = ...; | ||
|
||
// Atom<null | RestrictedItem | Promise<null | RestrictedItem>> | ||
const restrictedItemAtom = atom((get) => | ||
pipe( | ||
get(isAdminAtom), | ||
soon((isAdmin) => (isAdmin ? get(queryAtom) : null)) | ||
) | ||
); | ||
``` | ||
|
||
### Conditional dependency (multiple conditions) | ||
|
||
```ts | ||
// pipes allow for cleaner code when using `soon` directly. | ||
import { pipe } from 'remeda'; | ||
import { soon, soonAll } from 'jotai-derive'; | ||
|
||
// Atom<RestrictedItem | Promise<RestrictedItem>> | ||
const queryAtom = ...; | ||
|
||
// Atom<boolean | Promise<boolean>> | ||
const isAdminAtom = ...; | ||
|
||
// Atom<boolean | Promise<boolean>> | ||
const enabledAtom = ...; | ||
|
||
// Atom<null | RestrictedItem | Promise<null | RestrictedItem>> | ||
const restrictedItemAtom = atom((get) => | ||
pipe( | ||
soonAll(get(isAdminAtom), get(enabledAtom)), | ||
soon(([isAdmin, enabled]) => (isAdmin && enabled ? get(queryAtom) : null)) | ||
) | ||
); | ||
|
||
``` | ||
|
||
## Demo | ||
|
||
<CodeSandbox id="jotai-derive-example-7422pk" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
--- | ||
title: History | ||
description: A Jōtai utility package for advanced state history management | ||
nav: 5.02 | ||
keywords: history,undo,redo,jotai | ||
--- | ||
|
||
[jotai-history](https://github.com/jotaijs/jotai-history) is a utility package for advanced state history management. | ||
|
||
## install | ||
|
||
``` | ||
npm i jotai-history | ||
``` | ||
|
||
## withHistory | ||
|
||
### Signature | ||
|
||
```ts | ||
declare function withHistory<T>(targetAtom: Atom<T>, limit: number): Atom<T[]> | ||
``` | ||
|
||
This function creates an atom that keeps a history of states for a given `targetAtom`. The `limit` parameter determines the maximum number of history states to keep. | ||
This is useful for tracking the changes over time. | ||
|
||
The history atom tracks changes to the `targetAtom` and maintains a list of previous states up to the specified `limit`. When the `targetAtom` changes, its new state is added to the history. | ||
|
||
### Usage | ||
|
||
```jsx | ||
import { atom, useAtomValue, useSetAtom } from 'jotai' | ||
import { withHistory } from 'jotai-history' | ||
const countAtom = atom(0) | ||
const countWithPrevious = withHistory(countAtom, 2) | ||
function CountComponent() { | ||
const [count, previousCount] = useAtomValue(countWithPrevious) | ||
const setCount = useSetAtom(countAtom) | ||
return ( | ||
<> | ||
<p>Count: {count}</p> | ||
<p>Previous Count: {previousCount}</p> | ||
<button onClick={() => setCount((c) => c + 1)}>Increment</button> | ||
</> | ||
) | ||
} | ||
``` | ||
|
||
## withUndo | ||
|
||
### Signature | ||
|
||
```ts | ||
type Undoable = { | ||
undo: () => void | ||
redo: () => void | ||
canUndo: boolean | ||
canRedo: boolean | ||
} | ||
declare function withUndo<T>( | ||
targetAtom: WritableAtom<T, [T], any>, | ||
limit: number, | ||
): Atom<Undoable> | ||
``` | ||
|
||
`withHistory` provides undo and redo capabilities for an atom. It keeps track of the value history of `targetAtom` and provides methods to move back and forth through that history. | ||
|
||
The returned object includes: | ||
|
||
- `undo`: A function to revert to the previous state. | ||
- `redo`: A function to advance to the next state. | ||
- `canUndo`: A boolean indicating if it's possible to undo. | ||
- `canRedo`: A boolean indicating if it's possible to redo. | ||
|
||
### Usage | ||
|
||
```jsx | ||
import { atom, useAtom, useAtomValue } from 'jotai' | ||
import { withUndo } from 'jotai-history' | ||
const counterAtom = atom(0) | ||
const undoCounterAtom = withUndo(counterAtom, 5) | ||
function CounterComponent() { | ||
const { undo, redo, canUndo, canRedo } = useAtomValue(undoCounterAtom) | ||
const [value, setValue] = useAtom(counterAtom) | ||
return ( | ||
<> | ||
<p>Count: {value}</p> | ||
<button onClick={() => setValue((c) => c + 1)}>Increment</button> | ||
<button onClick={undo} disabled={!canUndo}> | ||
Undo | ||
</button> | ||
<button onClick={redo} disabled={!canRedo}> | ||
Redo | ||
</button> | ||
</> | ||
) | ||
} | ||
``` | ||
|
||
<CodeSandbox id="g6qj3q" /> | ||
|
||
## Memory Management | ||
|
||
⚠️ Since `withHistory` and `withUndo` keeps a history of states, it's important to manage memory by setting a reasonable `limit`. Excessive history can lead to memory bloat, especially in applications with frequent state updates. |