From 82add2f33bc8bf862d7d03ae5111d9f1547a78d1 Mon Sep 17 00:00:00 2001 From: David Maskasky Date: Mon, 4 Mar 2024 21:01:52 -0800 Subject: [PATCH] atomWithUndo does not return the target value --- README.md | 7 +-- history.mdx | 99 ------------------------------------------ src/atomWithHistory.ts | 12 ++--- src/atomWithUndo.ts | 48 ++++++++++---------- 4 files changed, 32 insertions(+), 134 deletions(-) delete mode 100644 history.mdx diff --git a/README.md b/README.md index 5f06a59..9e94e2e 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,15 @@ The history atom tracks changes to the `targetAtom` and maintains a list of prev ### Usage ```tsx -import { atom } from 'jotai' +import { atom, useAtomValue, useSetAtom } from 'jotai' import { atomWithHistory } from 'jotai/utils' const countAtom = atom(0) const countWithPrevious = atomWithHistory(myAtom, 2) export function CountComponent() { - const [[count, previousCount], setCount] = useAtom(countWithPrevious) + const [count, previousCount] = useAtomValue(countWithPrevious) + const setCount = useSetAtom(countAtom) return ( <> @@ -69,7 +70,7 @@ The returned object includes: ### Usage ```tsx -import { atom } from 'jotai' +import { atom, useAtom, useAtomValue } from 'jotai' import { atomWithUndo } from 'jotai/utils' const counterAtom = atom(0) diff --git a/history.mdx b/history.mdx deleted file mode 100644 index abca0ff..0000000 --- a/history.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: History -nav: 3.6 -keywords: history, undo, redo ---- - -## atomWithHistory - -### Signature - -```ts -function atomWithHistory(targetAtom: Atom, limit: number): Atom -``` - -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 - -```ts -import { atom } from 'jotai' -import { atomWithHistory } from 'jotai/utils' - -const countAtom = atom(0) -const countWithPrevious = atomWithHistory(myAtom, 2) - -export function CountComponent() { - const [[count, previousCount], setCount] = useAtom(countWithPrevious) - - return ( - <> -

Count: {count}

-

Previous Count: {previousCount}

- - - ) -} -``` - -## atomWithUndo - -### Signature - -```ts -function atomWithUndo( - targetAtom: PrimitiveAtom, - limit: number -): Atom<{ - value: T - undo: Function - redo: Function - canUndo: boolean - canRedo: boolean -}> -``` - -`atomWithHistory` 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: - -- `value`: The current value of the `targetAtom`. -- `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 - -```tsx -import { atom } from 'jotai' -import { atomWithUndo } from 'jotai/utils' - -const counterAtom = atom(0) -const counterWithUndo = atomWithUndo(counterAtom, 5) - -export function CounterComponent() { - const [{ value, undo, redo, canUndo, canRedo }, setValue] = - useAtom(counterWithUndo) - - return ( - <> -

Count: {value}

- - - - - ) -} -``` - -## Memory Management - -⚠️ Since `atomWithHistory` and `atomWithUndo` keep 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. diff --git a/src/atomWithHistory.ts b/src/atomWithHistory.ts index 993adda..78a53d3 100644 --- a/src/atomWithHistory.ts +++ b/src/atomWithHistory.ts @@ -8,19 +8,13 @@ import type { Atom } from 'jotai/vanilla' */ export function atomWithHistory(targetAtom: Atom, limit: number) { const refAtom = atom( - () => ({ - history: [] as T[], - }), - (get) => () => { - get(refAtom).history.length = 0 - } + () => ({ history: [] as T[] }), + (get, _set) => () => void (get(refAtom).history.length = 0) ) refAtom.onMount = (mount) => mount() refAtom.debugPrivate = true return atom((get) => { const ref = get(refAtom) - const value = get(targetAtom) - ref.history = [value, ...ref.history].slice(0, limit) - return ref.history + return (ref.history = [get(targetAtom), ...ref.history].slice(0, limit)) }) } diff --git a/src/atomWithUndo.ts b/src/atomWithUndo.ts index dd411d6..b9ce03a 100644 --- a/src/atomWithUndo.ts +++ b/src/atomWithUndo.ts @@ -19,11 +19,16 @@ export function atomWithUndo(targetAtom: PrimitiveAtom, limit: number) { const UNDO = Symbol('undo') const REDO = Symbol('redo') type DoAction = typeof UNDO | typeof REDO - const refAtom = atom(() => ({ + const createRef = () => ({ index: 0, - stack: [] as T[][], + stack: [] as T[], action: null as DoAction | null, - })) + }) + const refAtom = atom( + createRef, + (get, _set) => () => void Object.assign(get(refAtom), createRef()) + ) + refAtom.onMount = (mount) => mount() refAtom.debugPrivate = true const updateRefAtom = atom( (get) => { @@ -36,16 +41,16 @@ export function atomWithUndo(targetAtom: PrimitiveAtom, limit: number) { // Remove future states if any ref.stack = ref.stack.slice(0, ref.index + 1) // Push the current state to the history - ref.stack.push(history.slice()) + ref.stack.push(history[0] as T) // Limit the history ref.stack = ref.stack.slice(-limit) // Move the current index to the end ref.index = ref.stack.length - 1 } - return null + return { ...ref } }, (get) => { - get(refAtom).stack = [[get(targetAtom)]] + get(refAtom).stack = [get(targetAtom)] return () => { const ref = get(refAtom) ref.index = 0 @@ -55,27 +60,24 @@ export function atomWithUndo(targetAtom: PrimitiveAtom, limit: number) { ) updateRefAtom.onMount = (mount) => mount() updateRefAtom.debugPrivate = true + const canUndoAtom = atom((get) => get(updateRefAtom).index > 0) + const canRedoAtom = atom((get) => { + const ref = get(updateRefAtom) + return ref.index < ref.stack.length - 1 + }) const baseAtom = atom( - (get, { setSelf }) => { - get(updateRefAtom) - - return { - undo: () => setSelf(UNDO), - redo: () => setSelf(REDO), - get canUndo() { - return get(refAtom).index > 0 - }, - get canRedo() { - return get(refAtom).index < get(refAtom).stack.length - 1 - }, - } - }, + (get, { setSelf }) => ({ + undo: () => setSelf(UNDO), + redo: () => setSelf(REDO), + canUndo: get(canUndoAtom), + canRedo: get(canRedoAtom), + }), (get, set, update) => { const ref = get(refAtom) const setCurrentState = () => { - const currentSlice = ref.stack[ref.index] - if (currentSlice?.[0] === undefined) return - set(targetAtom, currentSlice[0]) + const value = ref.stack[ref.index] + if (value === undefined) return + set(targetAtom, value as T) } if (update === UNDO) { if (get(baseAtom).canUndo) {