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

♻️ higher order reducer to add undo/redo functionality to redux state containers

License

NotificationsYou must be signed in to change notification settings

omnidan/redux-undo

Repository files navigation

NPM version (>=1.0)NPM DownloadsCoverage StatusDependenciesjs-standard-styleGitHub license

simple undo/redo functionality for redux state containers

https://i.imgur.com/M2KR4uo.gif

Protip: Check out thetodos-with-undo example or theredux-undo-boilerplate to quickly get started withredux-undo.

Switching from 0.x to 1.0: Make sure to update your programs to thelatest History API.

Help wanted: We are looking for volunteers to maintain this project, if you are interested, feel free to contact me atme@omnidan.net


This README is about the new 1.0 branch of redux-undo, if you are usingor plan on using 0.6, check outthe0.6 branch


Note on Imports

If you use Redux Undo in CommonJS environment,don’t forget to add.default to your import.

- var ReduxUndo = require('redux-undo')+ var ReduxUndo = require('redux-undo').default

If your environment support es modules just go by:

importReduxUndofrom'redux-undo';

We are also supporting UMD build:

varReduxUndo=window.ReduxUndo.default;

once again.default is required.

Installation

npm install --save redux-undo

API

importundoablefrom'redux-undo';undoable(reducer)undoable(reducer,config)

Making your reducers undoable

redux-undo is a reducer enhancer (higher-order reducer). It provides theundoable function, whichtakes an existing reducer and a configuration object and enhances your existingreducer with undo functionality.

Note: If you were accessingstate.counter before, you have to accessstate.present.counter after wrapping your reducer withundoable.

To install, firstly importredux-undo:

// Redux utility functionsimport{combineReducers}from'redux';// redux-undo higher-order reducerimportundoablefrom'redux-undo';

Then, addundoable to your reducer(s) like this:

combineReducers({counter:undoable(counter)})

Aconfiguration can be passed like this:

combineReducers({counter:undoable(counter,{limit:10// set a limit for the size of the history})})

Apply redux-undo magic to specific slice of your state.

When you expose an undo redo history action to your app users, you will not want those actionto apply on your whole redux state.Lets see this with naive document editor state.

constrootReducer=combineReducers({ui:uiReducer,document:documentReducer,})

wrapping the documentReducer with undoable higher order reducer

constrootReducer=combineReducers({ui:uiReducer,document:undoable(documentReducer),})

will provide only the document mountpoint of your state with an history.

an even more advanced usage would be to have many different mountpoint of your redux state, managedunder redux-undo.

constrootReducer=combineReducers({ui:uiReducer,document:undoable(documentReducer,{undoType:'DOCUMENT_UNDO',redoType:'DOCUMENT_REDO',// here you will want to configure specific redux-undo action type}),anotherDocument:undoable(documentReducer,{undoType:'ANOTHERDOCUMENT_UNDO',redoType:'ANOTHERDOCUMENT_REDO',// here you will want to configure specific redux-undo action type}),})

Don't forget to configure specific redux-undo action type for each of your mount point if you don'twant to see your different history to undo/redo in sync.

History API

Wrapping your reducer withundoable 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

Note: Your reducer still receives the current state, a.k.a.state.present. Therefore, you would not have to update an existing reducer to add undo functionality.

Undo/Redo Actions

Firstly, import the undo/redo action creators:

import{ActionCreators}from'redux-undo';

Then, you can usestore.dispatch() and the undo/redo action creators toperform undo/redo operations on your state:

store.dispatch(ActionCreators.undo())// undo the last actionstore.dispatch(ActionCreators.redo())// redo the last actionstore.dispatch(ActionCreators.jump(-2))// undo 2 stepsstore.dispatch(ActionCreators.jump(5))// redo 5 stepsstore.dispatch(ActionCreators.jumpToPast(index))// jump to requested index in the past[] arraystore.dispatch(ActionCreators.jumpToFuture(index))// jump to requested index in the future[] arraystore.dispatch(ActionCreators.clearHistory())// Remove all items from past[] and future[] arrays

Configuration

A configuration object can be passed toundoable() like this (values shownare default values):

undoable(reducer,{limit:false,// set to a number to turn on a limit for the historyfilter:()=>true,// see `Filtering Actions`groupBy:()=>null,// see `Grouping Actions`undoType:ActionTypes.UNDO,// define a custom action type for this undo actionredoType:ActionTypes.REDO,// define a custom action type for this redo actionjumpType:ActionTypes.JUMP,// define custom action type for this jump actionjumpToPastType:ActionTypes.JUMP_TO_PAST,// define custom action type for this jumpToPast actionjumpToFutureType:ActionTypes.JUMP_TO_FUTURE,// define custom action type for this jumpToFuture actionclearHistoryType:ActionTypes.CLEAR_HISTORY,// define custom action type for this clearHistory action// you can also pass an array of strings to define several action types that would clear the history// beware: those actions will not be passed down to the wrapped reducersinitTypes:['@@redux-undo/INIT'],// history will be (re)set upon init action type// beware: those actions will not be passed down to the wrapped reducersdebug:false,// set to `true` to turn on debuggingignoreInitialState:false,// prevent user from undoing to the beginning, ex: client-side hydrationneverSkipReducer:false,// prevent undoable from skipping the reducer on undo/redo and clearHistoryType actionssyncFilter:false// set to `true` to synchronize the `_latestUnfiltered` state with `present` when an excluded action is dispatched})

Note: If you want to use just theinitTypes functionality, but not importthe whole redux-undo library, useredux-recycle!

Initial State and History

You can use your redux store to set an initial history for your undoable reducers:

import{createStore}from'redux';constinitialHistory={past:[0,1,2,3],present:4,future:[5,6,7]}// Alternatively use the helper:// import { newHistory } from 'redux-undo';// const initialHistory = newHistory([0, 1, 2, 3], 4, [5, 6, 7]);conststore=createStore(undoable(counter),initialHistory);

Or just set the current state like you're used to with Redux. Redux-undo will create the history for you:

import{createStore}from'redux';conststore=createStore(undoable(counter),{foo:'bar'});// will make the state look like this:{past:[],present:{foo:'bar'},future:[]}

Grouping Actions

If you want to group your actions together into single undo/redo steps, youcan add agroupBy function toundoable.redux-undo providesgroupByActionTypes as a basicgroupBy function:

importundoable,{groupByActionTypes}from'redux-undo';undoable(reducer,{groupBy:groupByActionTypes(SOME_ACTION)})// or with arraysundoable(reducer,{groupBy:groupByActionTypes([SOME_ACTION])})

In these cases, consecutiveSOME_ACTION actions will be considered a singlestep in the undo/redo history.

CustomgroupBy Function

If you want to implement custom grouping behaviour, pass in your own functionwith the signature(action, currentState, previousHistory). If the returnvalue is notnull, then the new state will be grouped by that return value.If the next state is grouped into the same group as the previous state, thenthe two states will be grouped together in one step.

If the return value isnull, thenredux-undo will not group the next statewith the previous state.

ThegroupByActionTypes function essentially returns the following:

  • If a grouped action type (SOME_ACTION), the action type of the action (SOME_ACTION).
  • If not a grouped action type (any other action type),null.

WhengroupBy groups a state change, the associatedgroup will be savedalongsidepast,present, andfuture so that it may be referenced by thenext state change.

After an undo/redo/jump occurs, the current group gets reset tonull so thatthe undo/redo history is remembered.

Filtering Actions

If you don't want to include every action in the undo/redo history, you can addafilter function toundoable. This is useful for, for example, excludingactions that were not triggered by the user.

redux-undo provides you with theincludeAction andexcludeAction helpersfor basic filtering. They should be imported like this:

importundoable,{includeAction,excludeAction}from'redux-undo';

Now you can use the helper functions:

undoable(reducer,{filter:includeAction(SOME_ACTION)})undoable(reducer,{filter:excludeAction(SOME_ACTION)})// they even support Arrays:undoable(reducer,{filter:includeAction([SOME_ACTION,SOME_OTHER_ACTION])})undoable(reducer,{filter:excludeAction([SOME_ACTION,SOME_OTHER_ACTION])})

Note: Sincebeta4,only actions resulting in a new state are recorded. This means the(now deprecated)distinctState() filter is auto-applied.

Custom Filters

If you want to create your own filter, pass in a function with the signature(action, currentState, previousHistory). For example:

undoable(reducer,{filter:functionfilterActions(action,currentState,previousHistory){returnaction.type===SOME_ACTION;// only add to history if action is SOME_ACTION}})// The entire `history` state is available to your filter, so you can make// decisions based on past or future states:undoable(reducer,{filter:functionfilterState(action,currentState,previousHistory){let{ past, present, future}=previousHistory;returnfuture.length===0;// only add to history if future is empty}})

Combining Filters

You can also use our helper to combine filters.

importundoable,{combineFilters}from'redux-undo'functionisActionSelfExcluded(action){returnaction.wouldLikeToBeInHistory}functionareWeRecording(action,state){returnstate.recording}undoable(reducer,{filter:combineFilters(isActionSelfExcluded,areWeRecording)})

Ignoring Actions

When implementing a filter function, it only prevents the old state from beingstored in the history.filter does not prevent the present state from beingupdated.

If you want to ignore an action completely, as in, not even update the presentstate, you can make use ofredux-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)

What is this magic? How does it work?

Have a read of theImplementing Undo History recipe in the Redux documents, which explains in detail how redux-undo works.

Chat / Support

If you have a question or just want to discuss something with other redux-undo users/maintainers,chat with the community on discord (discord.gg/GbHZTmd33n)!

Also, look at the documentation over atredux-undo.js.org.

License

MIT, seeLICENSE.md for more information.

About

♻️ higher order reducer to add undo/redo functionality to redux state containers

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp