simple undo/redo functionality for redux state containers
Protip: You can use the redux-undo-boilerplate to quickly get started with redux-undo
.
Switching from 0.x to 1.0 (beta): Make sure to update your programs to the latest History API.
npm install --save redux-undo
Or you can install the beta:
npm install --save redux-undo@beta
import undoable from 'redux-undo';
undoable(reducer)
undoable(reducer, config)
redux-undo
is a reducer enhancer, it provides the undoable
function, which
takes an existing reducer and a configuration object and enhances your existing
reducer with undo functionality.
Note: If you were accessing state.counter
before, you have to access
state.counter.present
after wrapping your reducer with undoable
.
To install, firstly import redux-undo
:
// Redux utility functions
import { combineReducers } from 'redux';
// redux-undo higher-order reducer
import undoable from 'redux-undo';
Then, add undoable
to your reducer(s) like this:
combineReducers({
counter: undoable(counter)
})
A configuration can be passed like this:
combineReducers({
counter: undoable(counter, {
limit: 10 // set a limit for the history
})
})
Wrapping your reducer with undoable
makes the state look like this:
{
past: [...pastStatesHere...],
present: {...currentStateHere...},
future: [...futureStatesHere...]
}
Now you can get your current state like this: state.present
And you can access all past states (e.g. to show a history) like this: state.past
Firstly, import the undo/redo action creators:
import { ActionCreators } from 'redux-undo';
Then, you can use store.dispatch()
and the undo/redo action creators to
perform undo/redo operations on your state:
store.dispatch(ActionCreators.undo()) // undo the last action
store.dispatch(ActionCreators.redo()) // redo the last action
store.dispatch(ActionCreators.jumpToPast(index)) // jump to requested index in the past[] array
store.dispatch(ActionCreators.jumpToFuture(index)) // jump to requested index in the future[] array
store.dispatch(ActionCreators.clearHistory()) // Remove all items from past[] and future[] arrays
A configuration object can be passed to undoable()
like this (values shown
are default values):
undoable(reducer, {
limit: false, // set to a number to turn on a limit for the history
filter: () => true, // see `Filtering Actions` section
undoType: ActionTypes.UNDO, // define a custom action type for this undo action
redoType: ActionTypes.REDO, // define a custom action type for this redo action
jumpToPastType: ActionTypes.JUMP_TO_PAST, // define custom action type for this jumpToPast action
jumpToFutureType: ActionTypes.JUMP_TO_FUTURE, // define custom action type for this jumpToFuture action
clearHistoryType: ActionTypes.CLEAR_HISTORY, // define custom action type for this clearHistory action
initialState: undefined, // initial state (e.g. for loading)
initTypes: ['@@redux/INIT', '@@INIT'] // history will be (re)set upon init action type
initialHistory: { // initial history (e.g. for loading)
past: [],
present: config.initialState,
future: []
},
debug: false, // set to `true` to turn on debugging
})
Note: If you want to use just the initTypes
functionality, but not import
the whole redux-undo library, use redux-recycle!
If you don't want to include every action in the undo/redo history, you can
pass a function to undoable
like this:
undoable(reducer, {
filter: function filterActions(action, currentState, previousState) {
return action.type === SOME_ACTION; // only add to history if action is SOME_ACTION
}
})
// or you could do...
undoable(reducer, {
filter: function filterState(action, currentState, previousState) {
return currentState !== previousState; // only add to history if state changed
}
})
Or you can use the distinctState
, includeAction
and excludeAction
helpers,
which should be imported like this:
import undoable, { distinctState, includeAction, excludeAction } from 'redux-undo';
Now you can use the helper, which is pretty simple:
undoable(reducer, { filter: includeAction(SOME_ACTION) })
undoable(reducer, { filter: excludeAction(SOME_ACTION) })
// or you could do...
undoable(reducer, { filter: distinctState() })
... they even support Arrays:
undoable(reducer, { filter: includeAction([SOME_ACTION, SOME_OTHER_ACTION]) })
undoable(reducer, { filter: excludeAction([SOME_ACTION, SOME_OTHER_ACTION]) })
When implementing a filter function, it only prevents the old state from being
stored in the history. filter
does not prevent the present state from being
updated.
If you want to ignore an action completely, as in, not even update the present state, you can make use of redux-ignore.
It can be used like this:
import { ignoreActions } from 'redux-ignore'
ignoreActions(
undoable(reducer),
[IGNORED_ACTION, ANOTHER_IGNORED_ACTION]
)
// or define your own function:
ignoreActions(
undoable(reducer),
(action) => action.type === SOME_ACTION // only add to history if action is SOME_ACTION
)
Have a read of the Implementing Undo History recipe in the Redux documents, which explains in detail how redux-undo works.
If you have a question or just want to discuss something with other redux-undo users/maintainers, chat with the community on gitter.im/omnidan/redux-undo
MIT, see LICENSE.md
for more information.