Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A React hook for state time travel with undo, redo, reset and archive functionalities.

License

NotificationsYou must be signed in to change notification settings

mutativejs/use-travel

Repository files navigation

Node CICoverage Statusnpmlicense

A React hook for state time travel with undo, redo, reset and archive functionalities withTravels.

Motivation

use-travel is a small and high-performance library for state time travel. It's built onMutative andTravels to support mutation updating immutable data. It's designed to be simple and easy to use, and it's also customizable for different use cases.

It's suitable for building any time travel feature in your application.

Installation

npm install use-travel mutative travels# oryarn add use-travel mutative travels# orpnpm add use-travel mutative travels

Features

  • Undo/Redo/Reset/Go/Archive functionalities
  • Mutations update immutable data
  • Small size for time travel with JSON Patch history
  • Customizable history size
  • Customizable initial patches
  • High performance
  • Mark function for custom immutability

Example

API

You can useuseTravel to create a time travel state. And it returns a tuple with the current state, the state setter, and the controls. The controls includeback(),forward(),reset(),canBack(),canForward(),canArchive(),getHistory(),patches,position,archive(), andgo().

import{useTravel}from'use-travel';constApp=()=>{const[state,setState,controls]=useTravel(0,{maxHistory:10,initialPatches:{patches:[],inversePatches:[],},});return(<div><div>{state}</div><buttononClick={()=>setState(state+1)}>Increment</button><buttononClick={()=>setState(state-1)}>Decrement</button><buttononClick={()=>controls.back()}disabled={!controls.canBack()}>        Undo</button><buttononClick={()=>controls.forward()}disabled={!controls.canForward()}>        Redo</button><buttononClick={controls.reset}>Reset</button>{controls.getHistory().map((state,index)=>(<divkey={index}>{state}</div>))}{controls.patches.patches.map((patch,index)=>(<divkey={index}>{JSON.stringify(patch)}</div>))}<div>{controls.position}</div><buttononClick={()=>{controls.go(1);}}>        Go</button></div>);};

Parameters

Parametertypedescriptiondefault
maxHistorynumberThe maximum number of history to keep10
initialPatchesTravelPatchesThe initial patches{patches: [],inversePatches: []}
initialPositionnumberThe initial position of the state0
autoArchivebooleanAuto archive the state (seeArchive Mode for details)true
enableAutoFreezebooleanEnable auto freeze the state,view morefalse
strictbooleanEnable strict mode,view morefalse
markMark<O, F>[]The mark function ,view more() => void

Returns

Returntypedescription
stateValue<S, F>The current state
setStateUpdater<InitialValue>The state setter, support mutation update or return immutable data
controls.back(amount?: number) => voidGo back to the previous state
controls.forward(amount?: number) => voidGo forward to the next state
controls.reset() => voidReset the state to the initial state
controls.canBack() => booleanCheck if can go back to the previous state
controls.canForward() => booleanCheck if can go forward to the next state
controls.canArchive() => booleanCheck if can archive the current state
controls.getHistory() => T[]Get the history of the state
controls.patchesTravelPatches[]Get the patches history of the state
controls.positionnumberGet the current position of the state
controls.go(nextPosition: number) => voidGo to the specific position of the state
controls.archive() => voidArchive the current state(theautoArchive options should befalse)

useTravelStore

When you need to manage a singleTravels instance outside of React—e.g. to share the same undo/redo history across multiple components—create the store manually and bind it withuseTravelStore. The hook keeps React in sync with the external store, exposes the same controls object, and rejects mutable stores to ensure React can observe updates.

// store.tsimport{Travels}from'travels';exportconsttravels=newTravels({count:0});// mutable: true is not supported
// Counter.tsximport{useTravelStore}from'use-travel';import{travels}from'./store';exportfunctionCounter(){const[state,setState,controls]=useTravelStore(travels);return(<div><span>{state.count}</span><buttononClick={()=>setState((draft)=>{draft.count+=1;})}>        Increment</button><buttononClick={()=>controls.back()}disabled={!controls.canBack()}>        Undo</button></div>);}

useTravelStore stays reactive even when theTravels instance is updated elsewhere (for example, in services or other components) and forwards manual archive helpers when the store is created withautoArchive: false.

Archive Mode

use-travel provides two archive modes to control how state changes are recorded in history:

Auto Archive Mode (default:autoArchive: true)

In auto archive mode, everysetState call is automatically recorded as a separate history entry. This is the simplest mode and suitable for most use cases.

const[state,setState,controls]=useTravel({count:0});// or explicitly: useTravel({ count: 0 }, { autoArchive: true })// Each setState creates a new history entrysetState({count:1});// History: [0, 1]// ... user clicks another buttonsetState({count:2});// History: [0, 1, 2]// ... user clicks another buttonsetState({count:3});// History: [0, 1, 2, 3]controls.back();// Go back to count: 2

Manual Archive Mode (autoArchive: false)

In manual archive mode, you control when state changes are recorded to history using thearchive() function. This is useful when you want to group multiple state changes into a single undo/redo step.

Use Case 1: Batch multiple changes into one history entry

const[state,setState,controls]=useTravel({count:0},{autoArchive:false});// Multiple setState calls across different renderssetState({count:1});// Temporary change (not in history yet)// ... user clicks another buttonsetState({count:2});// Temporary change (not in history yet)// ... user clicks another buttonsetState({count:3});// Temporary change (not in history yet)// Commit all changes as a single history entrycontrols.archive();// History: [0, 3]// Now undo will go back to 0, not 2 or 1controls.back();// Back to 0

Use Case 2: Explicit commit after a single change

functionhandleSave(){setState((draft)=>{draft.count+=1;});controls.archive();// Commit immediately}

The key difference:

  • Auto archive: EachsetState = one undo step
  • Manual archive:archive() call = one undo step (can include multiplesetState calls)

Important Notes

⚠️ setState Restriction:setState can only be calledonce 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.

constApp=()=>{const[state,setState,controls]=useTravel({count:0,todo:[]});return(<div><div>{state.count}</div><buttononClick={()=>{// ❌ Multiple setState calls in the same event handlersetState((draft)=>{draft.count+=1;});setState((draft)=>{draft.todo.push({id:1,text:'Buy'});});// This will throw: "setState cannot be called multiple times in the same render cycle"// ✅ Correct: Batch all changes in a single setStatesetState((draft)=>{draft.count+=1;draft.todo.push({id:1,text:'Buy'});});}}>        Update</button></div>);};

Note: WithautoArchive: false, you can callsetState once per event handler across multiple renders, then callarchive() whenever you want to commit those changes to history.

Persistence

TravelPatches is the type of patches history, it includespatches andinversePatches.

If you want to persist the state, you can usestate/controls.patches/controls.position to save the travel history. Then, read the persistent data asinitialState,initialPatches, andinitialPosition when initializing the state, like this:

const[state,setState,controls]=useTravel(initialState,{  initialPatches,  initialPosition,});

License

use-travel isMIT licensed.

About

A React hook for state time travel with undo, redo, reset and archive functionalities.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp