Skip to main content

Redux Methods

When imported from history-adapter/redux, the createHistoryAdapter function returns an object with additional methods for use with Redux Toolkit.

import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
Redux Toolkit

RTK is an optional peer dependency, but required to use the Redux specific entry point. Make sure you have it installed:


npm install @reduxjs/toolkit

All standard methods are available, plus the following:

undoableReducer

A wrapper around the undoable method that receives a case reducer and returns a wrapped reducer that manages undo and redo state.

Instead of providing an isUndoable callback, the reducer checks action.meta.undoable to determine if the action should be tracked.

info

In order to set action.meta.undoable, Redux Toolkit requires use of prepare callbacks.

adapter.withPayload and adapter.withoutPayload are provided for common cases.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: counterAdapter.getInitialState({ value: 0 }),
reducers: {
  incremented: counterAdapter.undoableReducer((draft) => {
    draft.value += 1;
  }),
  incrementedBy: counterAdapter.undoableReducer(
    (draft, action: PayloadAction<number>) => {
      draft.value += action.payload;
    },
  ),
},
});

const { incremented, incrementedBy } = counterSlice.actions;

const store = makePrintStore(counterSlice.reducer);

store.dispatch(incremented());

store.dispatch(incrementedBy(5));

It still accepts other configuration options, such as selectHistoryState.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: { counter: counterAdapter.getInitialState({ value: 0 }) },
reducers: {
  incremented: counterAdapter.undoableReducer(
    (draft) => {
      draft.value += 1;
    },
    {
      selectHistoryState: (state: NestedState) => state.counter,
    },
  ),
},
});

const { incremented } = counterSlice.actions;

const store = makePrintStore(counterSlice.reducer);

store.dispatch(incremented());

Other methods

Other methods that update state (such as undo, redo, etc.) are valid case reducers and can be used with createSlice.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: counterAdapter.getInitialState({ value: 0 }),
reducers: {
  incremented: counterAdapter.undoableReducer((draft) => {
    draft.value += 1;
  }),
  undone: counterAdapter.undo,
  redone: counterAdapter.redo,
  jumped: counterAdapter.jump,
  paused: counterAdapter.pause,
  resumed: counterAdapter.resume,
  historyCleared: counterAdapter.clearHistory,
},
});

const { incremented, undone, redone, jumped, paused, resumed, historyCleared } = counterSlice.actions;

const store = makePrintStore(counterSlice.reducer);

store.dispatch(incremented());

store.dispatch(undone());

store.dispatch(redone());

store.dispatch(jumped(-1));

withoutPayload

Creates a prepare callback which accepts a single (optional) argument, undoable. This is useful when you want to create an action that doesn't require a payload.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: counterAdapter.getInitialState({ value: 0 }),
reducers: {
  incremented: {
    prepare: counterAdapter.withoutPayload(),
    reducer: counterAdapter.undoableReducer((draft) => {
      draft.value += 1;
    }),
  },
},
});

const { incremented } = counterSlice.actions;

const store = makePrintStore(counterSlice.reducer);

// undefined means the action is undoable
store.dispatch(incremented());
store.dispatch(incremented(false));
store.dispatch(incremented(true));

withPayload

Creates a prepare callback which accepts two arguments, payload (optional if potentially undefined) and undoable (optional). This is useful when you want to create an action that requires a payload.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: counterAdapter.getInitialState({ value: 0 }),
reducers: {
  incrementedBy: {
    prepare: counterAdapter.withPayload<number>(),
    reducer: counterAdapter.undoableReducer(
      (draft, action: PayloadAction<number>) => {
        draft.value += action.payload;
      },
    ),
  },
},
});

const { incrementedBy } = counterSlice.actions;

const store = makePrintStore(counterSlice.reducer);

// undefined means the action is undoable
store.dispatch(incrementedBy(5));
store.dispatch(incrementedBy(5, false));
store.dispatch(incrementedBy(5, true));

getSelectors

Creates a set of useful selectors for the state managed by the adapter.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const counterSlice = createSlice({
name: "counter",
initialState: counterAdapter.getInitialState({ value: 0 }),
reducers: {
  incremented: counterAdapter.undoableReducer((draft) => {
    draft.value += 1;
  }),
},
selectors: {
  ...counterAdapter.getSelectors(),
},
});

const { incremented } = counterSlice.actions;

const { selectCanUndo, selectCanRedo, selectPresent, selectPaused } =
counterSlice.getSelectors();

const store = makePrintStore(counterSlice.reducer);

store.dispatch(incremented());

const print = getPrint();

print({
canUndo: selectCanUndo(store.getState()), // true if there are past states
canRedo: selectCanRedo(store.getState()), // true if there are future states
present: selectPresent(store.getState()),
paused: selectPaused(store.getState()),
});

An input selector can be provided if the state is nested, which will be used to select the relevant slice of state.

import "./styles.css";
import { makePrintStore } from "./reduxUtils";
import { getPrint } from "./utils";
import { createSlice } from "@reduxjs/toolkit";
import { createHistoryAdapter } from "history-adapter/redux";

interface CounterState {
  value: number;
}

const counterAdapter = createHistoryAdapter<CounterState>({ limit: 10 });
const initialState = { counter: counterAdapter.getInitialState({ value: 0 }) };

const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
  incremented: counterAdapter.undoableReducer(
    (draft) => {
      draft.value += 1;
    },
    {
      selectHistoryState: (state: typeof initialState) => state.counter,
    },
  ),
},
selectors: {
  ...counterAdapter.getSelectors((state: typeof initialState) => state.counter),
},
});

const { incremented } = counterSlice.actions;

const { selectCanUndo, selectCanRedo, selectPresent, selectPaused } =
counterSlice.getSelectors();

const store = makePrintStore(counterSlice.reducer);

store.dispatch(incremented());

const print = getPrint();

print({
canUndo: selectCanUndo(store.getState()), // true if there are past states
canRedo: selectCanRedo(store.getState()), // true if there are future states
present: selectPresent(store.getState()),
paused: selectPaused(store.getState()),
});