{"id":13526953,"url":"https://github.com/omnidan/redux-undo","last_synced_at":"2025-05-14T00:05:39.718Z","repository":{"id":37493248,"uuid":"42685034","full_name":"omnidan/redux-undo","owner":"omnidan","description":":recycle: higher order reducer to add undo/redo functionality to redux state containers","archived":false,"fork":false,"pushed_at":"2025-05-04T16:01:12.000Z","size":2280,"stargazers_count":2908,"open_issues_count":35,"forks_count":189,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-05-07T01:02:37.331Z","etag":null,"topics":["history","react","redo","redux","redux-state","redux-undo","undo"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omnidan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null},"funding":{"github":"omnidan"}},"created_at":"2015-09-17T22:20:00.000Z","updated_at":"2025-05-06T20:28:29.000Z","dependencies_parsed_at":"2023-02-02T20:46:26.171Z","dependency_job_id":"2c9a5817-a506-42c5-8424-24afde828065","html_url":"https://github.com/omnidan/redux-undo","commit_stats":{"total_commits":311,"total_committers":43,"mean_commits":7.232558139534884,"dds":0.6720257234726688,"last_synced_commit":"370a19d8640c0f239037ae3da218181b2606c7f8"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omnidan%2Fredux-undo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omnidan%2Fredux-undo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omnidan%2Fredux-undo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omnidan%2Fredux-undo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omnidan","download_url":"https://codeload.github.com/omnidan/redux-undo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252989947,"owners_count":21836667,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["history","react","redo","redux","redux-state","redux-undo","undo"],"created_at":"2024-08-01T06:01:38.279Z","updated_at":"2025-05-14T00:05:39.712Z","avatar_url":"https://github.com/omnidan.png","language":"JavaScript","funding_links":["https://github.com/sponsors/omnidan"],"categories":["JavaScript","Utilities","Uncategorized","Marks","Redux [🔝](#readme)"],"sub_categories":["Uncategorized","[React - A JavaScript library for building user interfaces](http://facebook.github.io/react)"],"readme":"# redux undo/redo\n\n[![NPM version (\u003e=1.0)](https://img.shields.io/npm/v/redux-undo.svg?style=flat-square)](https://www.npmjs.com/package/redux-undo) [![NPM Downloads](https://img.shields.io/npm/dm/redux-undo.svg?style=flat-square)](https://www.npmjs.com/package/redux-undo) [![Coverage Status](https://img.shields.io/coveralls/omnidan/redux-undo.svg?style=flat-square)](https://coveralls.io/r/omnidan/redux-undo) [![Dependencies](https://img.shields.io/david/omnidan/redux-undo.svg?style=flat-square)](https://david-dm.org/omnidan/redux-undo) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/omnidan/redux-undo/master/LICENSE.md)\n\n_simple undo/redo functionality for redux state containers_\n\n[![https://i.imgur.com/M2KR4uo.gif](https://i.imgur.com/M2KR4uo.gif)](https://github.com/omnidan/redux-undo-boilerplate)\n\n**Protip:** Check out the [todos-with-undo example](https://github.com/omnidan/redux-undo/tree/master/examples/todos-with-undo) or the [redux-undo-boilerplate](https://github.com/omnidan/redux-undo-boilerplate) to quickly get started with `redux-undo`.\n\n**Switching from 0.x to 1.0:** Make sure to update your programs to the [latest History API](#history-api).\n\n**Help wanted:** We are looking for volunteers to maintain this project, if you are interested, feel free to contact me at [me@omnidan.net](mailto:me@omnidan.net)\n\n---\n\n**This README is about the new 1.0 branch of redux-undo, if you are using\nor plan on using 0.6, check out [the `0.6` branch](https://github.com/omnidan/redux-undo/tree/0.6)**\n\n---\n\n## Note on Imports\n\nIf you use Redux Undo in CommonJS environment, **don’t forget to add `.default` to your import**.\n\n```diff\n- var ReduxUndo = require('redux-undo')\n+ var ReduxUndo = require('redux-undo').default\n```\n\nIf your environment support es modules just go by:\n\n```js\nimport ReduxUndo from 'redux-undo';\n```\n\nWe are also supporting UMD build:\n\n```js\nvar ReduxUndo = window.ReduxUndo.default;\n```\n\n**once again `.default` is required.**\n\n## Installation\n\n```\nnpm install --save redux-undo\n```\n\n\n## API\n\n```js\nimport undoable from 'redux-undo';\nundoable(reducer)\nundoable(reducer, config)\n```\n\n\n## Making your reducers undoable\n\n`redux-undo` is a reducer enhancer (higher-order reducer). It provides the `undoable` function, which\ntakes an existing reducer and a configuration object and enhances your existing\nreducer with undo functionality.\n\n**Note:** If you were accessing `state.counter` before, you have to access\n`state.present.counter` after wrapping your reducer with `undoable`.\n\nTo install, firstly import `redux-undo`:\n\n```js\n// Redux utility functions\nimport { combineReducers } from 'redux';\n// redux-undo higher-order reducer\nimport undoable from 'redux-undo';\n```\n\nThen, add `undoable` to your reducer(s) like this:\n\n```js\ncombineReducers({\n  counter: undoable(counter)\n})\n```\n\nA [configuration](#configuration) can be passed like this:\n\n```js\ncombineReducers({\n  counter: undoable(counter, {\n    limit: 10 // set a limit for the size of the history\n  })\n})\n```\n\n## Apply redux-undo magic to specific slice of your state.\nWhen you expose an undo redo history action to your app users, you will not want those action \nto apply on your whole redux state.\nLets see this with naive document editor state.\n\n```js\nconst rootReducer = combineReducers({\n  ui: uiReducer,\n  document: documentReducer,\n})\n```\n\nwrapping the documentReducer with undoable higher order reducer\n\n```js\nconst rootReducer = combineReducers({\n  ui: uiReducer,\n  document: undoable(documentReducer),\n})\n```\nwill provide only the document mountpoint of your state with an history.\n\nan even more advanced usage would be to have many different mountpoint of your redux state, managed\nunder redux-undo.\n```js\nconst rootReducer = combineReducers({\n  ui: uiReducer,\n  document: undoable(documentReducer, {\n    undoType: 'DOCUMENT_UNDO',\n    redoType: 'DOCUMENT_REDO',\n    // here you will want to configure specific redux-undo action type  \n  }),\n  anotherDocument: undoable(documentReducer, {\n    undoType: 'ANOTHERDOCUMENT_UNDO',\n    redoType: 'ANOTHERDOCUMENT_REDO',\n    // here you will want to configure specific redux-undo action type  \n  }),\n})\n```\nDon't forget to configure specific redux-undo action type for each of your mount point if you don't\nwant to see your different history to undo/redo in sync.\n\n## History API\n\nWrapping your reducer with `undoable` makes the state look like this:\n\n```js\n{\n  past: [...pastStatesHere...],\n  present: {...currentStateHere...},\n  future: [...futureStatesHere...]\n}\n```\n\nNow you can get your current state like this: `state.present`\n\nAnd you can access all past states (e.g. to show a history) like this: `state.past`\n\n**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.\n\n\n## Undo/Redo Actions\n\nFirstly, import the undo/redo action creators:\n\n```js\nimport { ActionCreators } from 'redux-undo';\n```\n\nThen, you can use `store.dispatch()` and the undo/redo action creators to\nperform undo/redo operations on your state:\n\n```js\nstore.dispatch(ActionCreators.undo()) // undo the last action\nstore.dispatch(ActionCreators.redo()) // redo the last action\n\nstore.dispatch(ActionCreators.jump(-2)) // undo 2 steps\nstore.dispatch(ActionCreators.jump(5)) // redo 5 steps\n\nstore.dispatch(ActionCreators.jumpToPast(index)) // jump to requested index in the past[] array\nstore.dispatch(ActionCreators.jumpToFuture(index)) // jump to requested index in the future[] array\n\nstore.dispatch(ActionCreators.clearHistory()) // Remove all items from past[] and future[] arrays\n```\n\n\n## Configuration\n\nA configuration object can be passed to `undoable()` like this (values shown\nare default values):\n\n```js\nundoable(reducer, {\n  limit: false, // set to a number to turn on a limit for the history\n\n  filter: () =\u003e true, // see `Filtering Actions`\n  groupBy: () =\u003e null, // see `Grouping Actions`\n\n  undoType: ActionTypes.UNDO, // define a custom action type for this undo action\n  redoType: ActionTypes.REDO, // define a custom action type for this redo action\n\n  jumpType: ActionTypes.JUMP, // define custom action type for this jump action\n\n  jumpToPastType: ActionTypes.JUMP_TO_PAST, // define custom action type for this jumpToPast action\n  jumpToFutureType: ActionTypes.JUMP_TO_FUTURE, // define custom action type for this jumpToFuture action\n\n  clearHistoryType: ActionTypes.CLEAR_HISTORY, // define custom action type for this clearHistory action\n  // you can also pass an array of strings to define several action types that would clear the history\n  // beware: those actions will not be passed down to the wrapped reducers\n\n  initTypes: ['@@redux-undo/INIT'], // history will be (re)set upon init action type\n  // beware: those actions will not be passed down to the wrapped reducers\n\n  debug: false, // set to `true` to turn on debugging\n  ignoreInitialState: false, // prevent user from undoing to the beginning, ex: client-side hydration\n\n  neverSkipReducer: false, // prevent undoable from skipping the reducer on undo/redo and clearHistoryType actions\n  syncFilter: false // set to `true` to synchronize the `_latestUnfiltered` state with `present` when an excluded action is dispatched\n})\n```\n\n**Note:** If you want to use just the `initTypes` functionality, but not import\nthe whole redux-undo library, use [redux-recycle](https://github.com/omnidan/redux-recycle)!\n\n### Initial State and History\n\nYou can use your redux store to set an initial history for your undoable reducers:\n\n```js\n\nimport { createStore } from 'redux';\n\nconst initialHistory = {\n  past: [0, 1, 2, 3],\n  present: 4,\n  future: [5, 6, 7]\n}\n\n// Alternatively use the helper:\n// import { newHistory } from 'redux-undo';\n// const initialHistory = newHistory([0, 1, 2, 3], 4, [5, 6, 7]);\n\nconst store = createStore(undoable(counter), initialHistory);\n\n```\n\nOr just set the current state like you're used to with Redux. Redux-undo will create the history for you:\n\n```js\n\nimport { createStore } from 'redux';\n\nconst store = createStore(undoable(counter), {foo: 'bar'});\n\n// will make the state look like this:\n{\n  past: [],\n  present: {foo: 'bar'},\n  future: []\n}\n\n```\n\n### Grouping Actions\n\nIf you want to group your actions together into single undo/redo steps, you\ncan add a `groupBy` function to `undoable`. `redux-undo` provides\n`groupByActionTypes` as a basic `groupBy` function:\n\n```js\nimport undoable, { groupByActionTypes } from 'redux-undo';\n\nundoable(reducer, { groupBy: groupByActionTypes(SOME_ACTION) })\n// or with arrays\nundoable(reducer, { groupBy: groupByActionTypes([SOME_ACTION]) })\n```\n\nIn these cases, consecutive `SOME_ACTION` actions will be considered a single\nstep in the undo/redo history.\n\n#### Custom `groupBy` Function\n\nIf you want to implement custom grouping behaviour, pass in your own function\nwith the signature `(action, currentState, previousHistory)`. If the return\nvalue is not `null`, then the new state will be grouped by that return value.\nIf the next state is grouped into the same group as the previous state, then\nthe two states will be grouped together in one step.\n\nIf the return value is `null`, then `redux-undo` will not group the next state\nwith the previous state.\n\nThe `groupByActionTypes` function essentially returns the following:\n* If a grouped action type (`SOME_ACTION`), the action type of the action (`SOME_ACTION`).\n* If not a grouped action type (any other action type), `null`.\n\nWhen `groupBy` groups a state change, the associated `group` will be saved\nalongside `past`, `present`, and `future` so that it may be referenced by the\nnext state change.\n\nAfter an undo/redo/jump occurs, the current group gets reset to `null` so that\nthe undo/redo history is remembered.\n\n### Filtering Actions\n\nIf you don't want to include every action in the undo/redo history, you can add\na `filter` function to `undoable`. This is useful for, for example, excluding\nactions that were not triggered by the user.\n\n`redux-undo` provides you with the `includeAction` and `excludeAction` helpers\nfor basic filtering. They should be imported like this:\n\n```js\nimport undoable, { includeAction, excludeAction } from 'redux-undo';\n```\n\nNow you can use the helper functions:\n\n```js\nundoable(reducer, { filter: includeAction(SOME_ACTION) })\nundoable(reducer, { filter: excludeAction(SOME_ACTION) })\n\n// they even support Arrays:\n\nundoable(reducer, { filter: includeAction([SOME_ACTION, SOME_OTHER_ACTION]) })\nundoable(reducer, { filter: excludeAction([SOME_ACTION, SOME_OTHER_ACTION]) })\n```\n\n**Note:** Since [`beta4`](https://github.com/omnidan/redux-undo/releases/tag/beta4),\n          only actions resulting in a new state are recorded. This means the\n          (now deprecated) `distinctState()` filter is auto-applied.\n\n#### Custom Filters\n\nIf you want to create your own filter, pass in a function with the signature\n`(action, currentState, previousHistory)`. For example:\n\n```js\nundoable(reducer, {\n  filter: function filterActions(action, currentState, previousHistory) {\n    return action.type === SOME_ACTION; // only add to history if action is SOME_ACTION\n  }\n})\n\n// The entire `history` state is available to your filter, so you can make\n// decisions based on past or future states:\n\nundoable(reducer, {\n  filter: function filterState(action, currentState, previousHistory) {\n    let { past, present, future } = previousHistory;\n    return future.length === 0; // only add to history if future is empty\n  }\n})\n```\n\n#### Combining Filters\n\nYou can also use our helper to combine filters.\n\n```js\nimport undoable, {combineFilters} from 'redux-undo'\n\nfunction isActionSelfExcluded(action) {\n  return action.wouldLikeToBeInHistory\n}\n\nfunction areWeRecording(action, state) {\n  return state.recording\n}\n\nundoable(reducer, {\n  filter: combineFilters(isActionSelfExcluded, areWeRecording)\n})\n```\n\n### Ignoring Actions\n\nWhen implementing a filter function, it only prevents the old state from being\nstored in the history. **`filter` does not prevent the present state from being\nupdated.**\n\nIf you want to ignore an action completely, as in, not even update the present\nstate, you can make use of [redux-ignore](https://github.com/omnidan/redux-ignore).\n\nIt can be used like this:\n\n```js\nimport { ignoreActions } from 'redux-ignore'\n\nignoreActions(\n  undoable(reducer),\n  [IGNORED_ACTION, ANOTHER_IGNORED_ACTION]\n)\n\n// or define your own function:\n\nignoreActions(\n  undoable(reducer),\n  (action) =\u003e action.type === SOME_ACTION // only add to history if action is SOME_ACTION\n)\n```\n\n\n## What is this magic? How does it work?\n\nHave a read of the [Implementing Undo History recipe](https://redux.js.org/recipes/implementing-undo-history) in the Redux documents, which explains in detail how redux-undo works.\n\n\n## Chat / Support\n\nIf 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)](https://discord.gg/GbHZTmd33n)!\n\nAlso, look at the documentation over at [redux-undo.js.org](https://redux-undo.js.org/).\n\n\n## License\n\nMIT, see `LICENSE.md` for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomnidan%2Fredux-undo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomnidan%2Fredux-undo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomnidan%2Fredux-undo/lists"}