{"id":20098475,"url":"https://github.com/bradleyboy/use-history-reducer","last_synced_at":"2025-05-06T05:32:08.380Z","repository":{"id":43817977,"uuid":"213097943","full_name":"bradleyboy/use-history-reducer","owner":"bradleyboy","description":"Experimental immer-based reducer with undo/redo, checkpoints, and forking.","archived":false,"fork":false,"pushed_at":"2022-02-12T15:10:34.000Z","size":105,"stargazers_count":5,"open_issues_count":10,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-25T01:53:42.981Z","etag":null,"topics":["history","immer","react","reducer","undo-redo"],"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/bradleyboy.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-06T02:20:01.000Z","updated_at":"2023-08-13T00:12:55.000Z","dependencies_parsed_at":"2022-09-15T02:11:00.674Z","dependency_job_id":null,"html_url":"https://github.com/bradleyboy/use-history-reducer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradleyboy%2Fuse-history-reducer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradleyboy%2Fuse-history-reducer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradleyboy%2Fuse-history-reducer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradleyboy%2Fuse-history-reducer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bradleyboy","download_url":"https://codeload.github.com/bradleyboy/use-history-reducer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252629229,"owners_count":21779171,"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","immer","react","reducer","undo-redo"],"created_at":"2024-11-13T17:03:59.166Z","updated_at":"2025-05-06T05:32:07.701Z","avatar_url":"https://github.com/bradleyboy.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# useHistoryReducer\n\nThis experimental hook provides a light wrapper over `useReducer` which provides undo/redo, forking, and checkpoints. It's built on top of [Immer](https://immerjs.github.io/immer/docs/introduction), so you also get the added benefits it provides.\n\n## Basic example\n\n```js\nimport React from \"react\";\nimport useHistoryReducer from \"use-history-reducer\";\n\nfunction reducer(state, action) {\n  if (action.type === \"increment\") {\n    state.count += 1;\n  }\n}\n\nfunction App() {\n  const [state, changes, dispatch] = useHistoryReducer(reducer, { count: 1 });\n\n  return (\n    \u003cdiv\u003e\n      \u003cmain\u003e{state.count}\u003c/main\u003e\n      \u003cbutton onClick={() =\u003e dispatch({ type: \"increment\" })}\u003e++\u003c/button\u003e\n      \u003cbutton\n        disabled={changes.counts.undos === 0}\n        onClick={() =\u003e changes.undo()}\n      \u003e\n        Undo\n      \u003c/button\u003e\n      \u003cbutton\n        disabled={changes.counts.redos === 0}\n        onClick={() =\u003e changes.redo()}\n      \u003e\n        Redo\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## Forking\n\nForking allows you to break off from the main set of changes temporarily, and either commit all the changes that happen during the fork at once (and in one changeset), or revert them altogether. One use case for this is an input that may change the state many times, and you want only the final value to be recorded.\n\n```js\nimport React from \"react\";\nimport useHistoryReducer from \"use-history-reducer\";\n\nfunction reducer(state, action) {\n  if (action.type === \"SET_COUNT\") {\n    state.count = action.payload;\n  }\n}\n\nfunction App() {\n  const [state, changes, dispatch] = useHistoryReducer(reducer, { count: 1 });\n\n  return (\n    \u003cdiv\u003e\n      \u003cmain\u003e{state.count}\u003c/main\u003e\n      \u003cinput\n        type=\"range\"\n        min=\"1\"\n        max=\"100\"\n        value={state.count}\n        onMouseDown={() =\u003e changes.fork()}\n        onMouseUp={() =\u003e changes.commit()}\n        onChange={e =\u003e dispatch({ type: \"SET_COUNT\", payload: e.target.value })}\n      /\u003e\n      \u003cbutton\n        disabled={changes.counts.undos === 0}\n        onClick={() =\u003e changes.undo()}\n      \u003e\n        Undo\n      \u003c/button\u003e\n      \u003cbutton\n        disabled={changes.counts.redos === 0}\n        onClick={() =\u003e changes.redo()}\n      \u003e\n        Redo\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## Checkpoints\n\nBy using checkpoints, you can get the the changes made since the last checkpoint. This makes it easy to track unsaved changes you haven't sent to your backend or whatever persistence layer you are using.\n\n```js\nimport React from \"react\";\nimport useHistoryReducer from \"use-history-reducer\";\n\nfunction reducer(state, action) {\n  if (action.type === \"increment\") {\n    state.count += 1;\n  }\n}\n\nfunction App() {\n  const [state, changes, dispatch] = useHistoryReducer(reducer, { count: 1 });\n\n  return (\n    \u003cdiv\u003e\n      \u003cmain\u003e{state.count}\u003c/main\u003e\n      \u003cbutton onClick={() =\u003e dispatch({ type: \"increment\" })}\u003e++\u003c/button\u003e\n      \u003cbutton\n        disabled={changes.counts.unchecked === 0}\n        onClick={() =\u003e {\n          const changes = changes.checkpoint();\n          // do something with the patches.\n        }}\n      \u003e\n        Save\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradleyboy%2Fuse-history-reducer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbradleyboy%2Fuse-history-reducer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradleyboy%2Fuse-history-reducer/lists"}