This is a middleware base on redux. You don't need to worry about your undo/redo code and the service code mess up. when some operation like add new item, paste item, drag item to resort, .etc. you just need to add a sign named $$UNDO_REDO_TYPE to your redux action's payload, talk to the middleware that this operation is supporting undo/redo features.
Unlike redux-undo, This middleware don't use the snapshot to cache the past/future state. Just cache the undo/redo type and some required params and your self-defined params. Combined with your custom undo/redo handler, to reach the undo/redo object. This will not make your redux state so giant and has a great performance.
Using npm:
$ npm i --save react-undoredo-middleware
In your reducer:
import {undoRedoHandlers, undoRedoTypes} from 'react-undoredo-middleware'
const counterReducer = (state, action) => {
const {payload} = action
switch(action.type) {
//...other code
case undoRedoTypes.DEFAULT_ADD_HISTORY:
state = undoRedoHandlers.DEFAULT_ADD_HISTORY(state, payload)
return {...state}
case undoRedoTypes.UNDO_ACTION:
state = undoRedoHandlers.UNDO_ACTION(state, payload)
return {...state}
case undoRedoTypes.REDO_ACTION:
state = undoRedoHandlers.REDO_ACTION(state, payload)
return {...state}
default:
return state
}
}
export default counterReducer
define your custom undo/redo handlers:
const undoHandlers = {
/**
*
* @param {*} state redux state
* @param {*} undoAction when redo something, we need to cache it to the undoAction
* @param {*} undoItem cache Item(like sometimes you remove a item but may recover it in the future)
*/
add: (state, undoAction, undoItem) => {
state.value -= 1
return state
}
}
const redoHandlers = {
/**
*
* @param {*} state redux state
* @param {*} undoAction when undo something, we need to cache it to the redoAction
*/
add: (state, redoAction) => {
state.value += 1
return state
}
}
export {
undoHandlers,
redoHandlers
}
register the middleware:
import {createStore, applyMiddleware} from 'redux'
import {undoRedo} from 'react-undoredo-middleware'
import logger from 'redux-logger'
import reducer from "./Modules"
import {undoHandlers, redoHandlers} from './customerHandlers'
const undoRedoWithExtraArgument = undoRedo.withExtraArgument({ customUndoHandlers: undoHandlers, customRedoHandlers: redoHandlers })
const store = createStore(reducer, applyMiddleware(undoRedoWithExtraArgument, logger))
export default store
import React from 'react';
import {connect} from 'react-redux'
import CounterActions from '../store/Modules/counter/actions'
import {undoRedoActions} from 'react-undoredo-middleware'
const Counter = (props) => {
const {counter, Increment, Decrement, UNDO_ACTION, REDO_ACTION} = props
return (
<div>
{counter}
<button onClick={() => Increment({$$UNDO_REDO_TYPE: 'add'})}>+</button>
<button onClick={() => Decrement({$$UNDO_REDO_TYPE: 'minus'})}>-</button><br/>
<button onClick={() => UNDO_ACTION()}>undo</button>
<button onClick={() => REDO_ACTION()}>redo</button>
</div>
);
}
export default connect(
state => ({
counter: state.Counter.value
}),
{
...CounterActions,
...undoRedoActions
}
)(Counter);
In order to handle with other complicated service. except $$UNDO_REDO_TYPE, we can add more options to the payload.
{
$$UNDO_REDO_TYPE,
index,
path,
newValue,
oldValue
}
Such as:
<button onClick={() => Decrement({$$UNDO_REDO_TYPE: 'minus', index: 0, path: [], newValue: 0, oldValue: -1})}>-</button><br/>
Those options will cache into undoStack:
Get those params in custom undo/redo handler:
const redoHandlers = {
/**
*
* @param {*} state redux state
* @param {*} undoAction when undo something, we need to cache it to the redoAction
*/
add: (state, redoAction) => {
const {index, path, newValue, oldValue, cacheItem} = redoAction
// handle complicated situation
return state
}
}
Those above are built-in params. If you need add your self-defined params into your undoStack, see below:
<button onClick={() => Decrement({$$UNDO_REDO_TYPE: 'minus'}, {customDefinedArg: 'hello'})}>-</button><br/>
In this demo, function Decrement defined as below:
Decrement: (payload, customerOptions) => ({
type: 'decrement',
payload,
customerOptions
})
Clone this project package source, then change dir into demo, after intsalling node_modules, you can run the simple demo of undo/redo. and see how it works.
See the package source for more details.