Standard Methods
This page documents the standard methods available when using a history adapter instance.
Adapter setup
import { createHistoryAdapter } from "history-adapter";
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
getInitialState
Receives an initial state value and returns a "history state" shape.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
print({ initialState })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
print({ initialState })
A standalone version of this method is available as getInitialState
.
import "./styles.css";
import { getInitialState } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
const initialState = getInitialState({ value: 0 });
print({ initialState })
import "./styles.css";
import { getInitialState } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
const initialState = getInitialState({ value: 0 });
print({ initialState })
undoable
Wraps an immer recipe to automatically manage undo and redo state. Because immer wraps the state in a draft, you can safely mutate the state directly.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const initialState = counterAdapter.getInitialState({ value: 0 });
const nextState = increment(initialState);
print({ initialState, nextState })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const initialState = counterAdapter.getInitialState({ value: 0 });
const nextState = increment(initialState);
print({ initialState, nextState })
All of the methods to update state (undo
, redo
, etc.) will act mutably when passed a draft, otherwise return a new state. The same applies to the function returned by undoable
.
import "./styles.css";
import { produce } from "immer";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const initialState = counterAdapter.getInitialState({ value: 0 });
const nextState = increment(initialState);
const withDraft = produce(initialState, (draft) => {
increment(draft);
});
print({
initialState: initialState.present.value,
nextState: nextState.present.value,
withDraft: withDraft.present.value,
})
import "./styles.css";
import { produce } from "immer";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const initialState = counterAdapter.getInitialState({ value: 0 });
const nextState = increment(initialState);
const withDraft = produce(initialState, (draft) => {
increment(draft);
});
print({
initialState: initialState.present.value,
nextState: nextState.present.value,
withDraft: withDraft.present.value,
})
By default, a change is assumed to be undoable, and is added to the history stack. To have finer control over this behaviour, you can pass an isUndoable
function as part of the optional configuration object. This function receives the same arguments as the recipe, and should return a boolean (or undefined
, in which case the change is assumed to be undoable).
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const incrementBy = counterAdapter.undoable(
(draft, amount: number) => {
draft.value += amount;
},
{
isUndoable: (amount) => amount !== 0,
},
);
const initialState = counterAdapter.getInitialState({ value: 0 });
const incrementedByZero = incrementBy(initialState, 0);
const incrementedByOne = incrementBy(incrementedByZero, 1);
print({ incrementedByZero, incrementedByOne })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const incrementBy = counterAdapter.undoable(
(draft, amount: number) => {
draft.value += amount;
},
{
isUndoable: (amount) => amount !== 0,
},
);
const initialState = counterAdapter.getInitialState({ value: 0 });
const incrementedByZero = incrementBy(initialState, 0);
const incrementedByOne = incrementBy(incrementedByZero, 1);
print({ incrementedByZero, incrementedByOne })
Nested history state
Sometimes the state you want to manage is nested within a larger object. In this case, you can pass a selectHistoryState
function to undoable
to extract the relevant state.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
interface RootState {
counter: HistoryState<CounterState>;
}
const increment = counterAdapter.undoable(
(draft) => {
draft.value += 1;
},
{
selectHistoryState: (state: RootState) => state.counter,
},
);
const initialState = { counter: counterAdapter.getInitialState({ value: 0 }) };
const nextState = increment(initialState);
print({ initialState, nextState })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
interface RootState {
counter: PatchHistoryState<CounterState>;
}
const increment = counterAdapter.undoable(
(draft) => {
draft.value += 1;
},
{
selectPatchHistoryState: (state: RootState) => state.counter,
},
);
const initialState = { counter: counterAdapter.getInitialState({ value: 0 }) };
const nextState = increment(initialState);
print({ initialState, nextState })
It should be a function that receives the wider state object and returns the history state object.
undo
, redo
, jump
These methods allow you to navigate the history stack.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const nextState = increment(initialState);
const undoneState = counterAdapter.undo(nextState);
const redoneState = counterAdapter.redo(undoneState);
const jumpedState = counterAdapter.jump(redoneState, -1);
print({ nextState, undoneState, redoneState, jumpedState })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const nextState = increment(initialState);
const undoneState = counterAdapter.undo(nextState);
const redoneState = counterAdapter.redo(undoneState);
const jumpedState = counterAdapter.jump(redoneState, -1);
print({ nextState, undoneState, redoneState, jumpedState })
clearHistory
Clears the history stack, while preserving the present state.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const nextState = increment(initialState);
const clearedState = counterAdapter.clearHistory(nextState);
print({ nextState, clearedState })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const nextState = increment(initialState);
const clearedState = counterAdapter.clearHistory(nextState);
print({ nextState, clearedState })
Pausing history
To pause recording of history, you can set the paused
flag in state to true
- pause
and resume
methods are provided for this purpose.
Changes made while paused will not be added to the history stack, but will still update the present state.
import "./styles.css";
import { createHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const pausedState = counterAdapter.pause(initialState);
const whilePaused = increment(pausedState);
const resumedState = counterAdapter.resume(whilePaused);
const undone = counterAdapter.undo(resumedState);
print({ initialState, pausedState, whilePaused, resumedState, undone })
import "./styles.css";
import { createPatchHistoryAdapter } from "history-adapter";
import { getPrint } from "./utils";
const print = getPrint();
interface CounterState {
value: number;
}
const counterAdapter = createPatchHistoryAdapter<CounterState>({ limit: 10 });
const initialState = counterAdapter.getInitialState({ value: 0 });
const increment = counterAdapter.undoable((draft) => {
draft.value += 1;
});
const pausedState = counterAdapter.pause(initialState);
const whilePaused = increment(pausedState);
const resumedState = counterAdapter.resume(whilePaused);
const undone = counterAdapter.undo(resumedState);
print({ initialState, pausedState, whilePaused, resumedState, undone })