You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`mark`| Mark<O, F>[]| The mark function , [view more](https://github.com/unadlib/mutative?tab=readme-ov-file#createstate-fn-options)| () => void |
@@ -115,8 +115,71 @@ const App = () => {
115
115
|`controls.go`| (nextPosition: number) => void | Go to the specific position of the state |
116
116
|`controls.archive`| () => void | Archive the current state(the `autoArchive` options should be `false`) |
117
117
118
-
Note:
119
-
> **Important**: ⚠️⚠️⚠️ `setState` can only be called once within the same synchronous call stack (for example, inside a single event handler). Subsequent calls throw an error so each undo step maps to exactly one update. Batch multiple mutations inside one updater callback (mutating the draft) or finish all updates before calling `archive()` when `autoArchive` is disabled.
118
+
### Archive Mode
119
+
120
+
`use-travel` provides two archive modes to control how state changes are recorded in history:
121
+
122
+
#### Auto Archive Mode (default: `autoArchive: true`)
123
+
124
+
In auto archive mode, every `setState` call is automatically recorded as a separate history entry. This is the simplest mode and suitable for most use cases.
In manual archive mode, you control when state changes are recorded to history using the `archive()` function. This is useful when you want to group multiple state changes into a single undo/redo step.
143
+
144
+
**Use Case 1: Batch multiple changes into one history entry**
// Multiple setState calls across different renders
152
+
setState({ count:1 }); // Temporary change (not in history yet)
153
+
// ... user clicks another button
154
+
setState({ count:2 }); // Temporary change (not in history yet)
155
+
// ... user clicks another button
156
+
setState({ count:3 }); // Temporary change (not in history yet)
157
+
158
+
// Commit all changes as a single history entry
159
+
controls.archive(); // History: [0, 3]
160
+
161
+
// Now undo will go back to 0, not 2 or 1
162
+
controls.back(); // Back to 0
163
+
```
164
+
165
+
**Use Case 2: Explicit commit after a single change**
166
+
167
+
```jsx
168
+
functionhandleSave() {
169
+
setState((draft) => {
170
+
draft.count+=1;
171
+
});
172
+
controls.archive(); // Commit immediately
173
+
}
174
+
```
175
+
176
+
The key difference:
177
+
-**Auto archive**: Each `setState` = one undo step
178
+
-**Manual archive**: `archive()` call = one undo step (can include multiple `setState` calls)
179
+
180
+
### Important Notes
181
+
182
+
> **⚠️ setState Restriction**: `setState` can only be called **once** within the same synchronous call stack (e.g., inside a single event handler). This ensures predictable undo/redo behavior where each history entry represents a clear, atomic change.
120
183
121
184
```jsx
122
185
constApp= () => {
@@ -126,20 +189,20 @@ const App = () => {
126
189
<div>{state.count}</div>
127
190
<button
128
191
onClick={() => {
129
-
//use-travel is not support batch setState calls, so you should use one setState call to update the state
192
+
//❌ Multiple setState calls in the same event handler
130
193
setState((draft) => {
131
194
draft.count+=1;
132
195
});
133
196
setState((draft) => {
134
197
draft.todo.push({ id:1, text:'Buy' });
135
198
});
136
-
//❌ This will throw an error, because setState can only be called once within the same synchronous call stack
199
+
// This will throw: "setState cannot be called multiple times in the same render cycle"
137
200
201
+
// ✅ Correct: Batch all changes in a single setState
138
202
setState((draft) => {
139
203
draft.count+=1;
140
204
draft.todo.push({ id:1, text:'Buy' });
141
205
});
142
-
// ✅ This will work
143
206
}}
144
207
>
145
208
Update
@@ -149,9 +212,11 @@ const App = () => {
149
212
};
150
213
```
151
214
152
-
> `TravelPatches` is the type of patches history, it includes `patches` and `inversePatches`.
215
+
> **Note**: With `autoArchive: false`, you can call `setState` once per event handler across multiple renders, then call `archive()` whenever you want to commit those changes to history.
216
+
217
+
### Persistence
153
218
154
-
> If you want to control the state travel manually, you can set the `autoArchive` option to `false`, and use the `controls.archive` function to archive the state.
219
+
> `TravelPatches` is the type of patches history, it includes `patches`and `inversePatches`.
155
220
156
221
> If you want to persist the state, you can use `state`/`controls.patches`/`controls.position` to save the travel history. Then, read the persistent data as `initialState`, `initialPatches`, and `initialPosition` when initializing the state, like this:
|`mark`| Mark<O, F>[]| The mark function , [view more](https://github.com/unadlib/mutative?tab=readme-ov-file#createstate-fn-options)| () => void |
@@ -119,8 +119,71 @@ const App = () => {
119
119
|`controls.go`| (nextPosition: number) => void | Go to the specific position of the state |
120
120
|`controls.archive`| () => void | Archive the current state(the `autoArchive` options should be `false`) |
121
121
122
-
Note:
123
-
> **Important**: ⚠️⚠️⚠️ `setState` can only be called once within the same synchronous call stack (for example, inside a single event handler). Subsequent calls throw an error so each undo step maps to exactly one update. Batch multiple mutations inside one updater callback (mutating the draft) or finish all updates before calling `archive()` when `autoArchive` is disabled.
122
+
### Archive Mode
123
+
124
+
`use-travel` provides two archive modes to control how state changes are recorded in history:
125
+
126
+
#### Auto Archive Mode (default: `autoArchive: true`)
127
+
128
+
In auto archive mode, every `setState` call is automatically recorded as a separate history entry. This is the simplest mode and suitable for most use cases.
In manual archive mode, you control when state changes are recorded to history using the `archive()` function. This is useful when you want to group multiple state changes into a single undo/redo step.
147
+
148
+
**Use Case 1: Batch multiple changes into one history entry**
// Multiple setState calls across different renders
156
+
setState({ count:1 }); // Temporary change (not in history yet)
157
+
// ... user clicks another button
158
+
setState({ count:2 }); // Temporary change (not in history yet)
159
+
// ... user clicks another button
160
+
setState({ count:3 }); // Temporary change (not in history yet)
161
+
162
+
// Commit all changes as a single history entry
163
+
controls.archive(); // History: [0, 3]
164
+
165
+
// Now undo will go back to 0, not 2 or 1
166
+
controls.back(); // Back to 0
167
+
```
168
+
169
+
**Use Case 2: Explicit commit after a single change**
170
+
171
+
```jsx
172
+
functionhandleSave() {
173
+
setState((draft) => {
174
+
draft.count+=1;
175
+
});
176
+
controls.archive(); // Commit immediately
177
+
}
178
+
```
179
+
180
+
The key difference:
181
+
-**Auto archive**: Each `setState` = one undo step
182
+
-**Manual archive**: `archive()` call = one undo step (can include multiple `setState` calls)
183
+
184
+
### Important Notes
185
+
186
+
> **⚠️ setState Restriction**: `setState` can only be called **once** within the same synchronous call stack (e.g., inside a single event handler). This ensures predictable undo/redo behavior where each history entry represents a clear, atomic change.
124
187
125
188
```jsx
126
189
constApp= () => {
@@ -130,20 +193,20 @@ const App = () => {
130
193
<div>{state.count}</div>
131
194
<button
132
195
onClick={() => {
133
-
//use-travel is not support batch setState calls, so you should use one setState call to update the state
196
+
//❌ Multiple setState calls in the same event handler
134
197
setState((draft) => {
135
198
draft.count+=1;
136
199
});
137
200
setState((draft) => {
138
201
draft.todo.push({ id:1, text:'Buy' });
139
202
});
140
-
//❌ This will throw an error, because setState can only be called once within the same synchronous call stack
203
+
// This will throw: "setState cannot be called multiple times in the same render cycle"
141
204
205
+
// ✅ Correct: Batch all changes in a single setState
142
206
setState((draft) => {
143
207
draft.count+=1;
144
208
draft.todo.push({ id:1, text:'Buy' });
145
209
});
146
-
// ✅ This will work
147
210
}}
148
211
>
149
212
Update
@@ -153,9 +216,11 @@ const App = () => {
153
216
};
154
217
```
155
218
156
-
> `TravelPatches` is the type of patches history, it includes `patches` and `inversePatches`.
219
+
> **Note**: With `autoArchive: false`, you can call `setState` once per event handler across multiple renders, then call `archive()` whenever you want to commit those changes to history.
220
+
221
+
### Persistence
157
222
158
-
> If you want to control the state travel manually, you can set the `autoArchive` option to `false`, and use the `controls.archive` function to archive the state.
223
+
> `TravelPatches` is the type of patches history, it includes `patches`and `inversePatches`.
159
224
160
225
> If you want to persist the state, you can use `state`/`controls.patches`/`controls.position` to save the travel history. Then, read the persistent data as `initialState`, `initialPatches`, and `initialPosition` when initializing the state, like this:
0 commit comments