{"id":20178233,"url":"https://github.com/mattkrick/redux-optimistic-ui","last_synced_at":"2025-05-15T23:07:42.379Z","repository":{"id":3372130,"uuid":"49238912","full_name":"mattkrick/redux-optimistic-ui","owner":"mattkrick","description":"a reducer enhancer to enable type-agnostic optimistic updates","archived":false,"fork":false,"pushed_at":"2023-01-12T10:02:17.000Z","size":808,"stargazers_count":691,"open_issues_count":21,"forks_count":36,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-08T22:39:38.680Z","etag":null,"topics":["optimistic-ui","reducer-enhancer","redux"],"latest_commit_sha":null,"homepage":null,"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/mattkrick.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2016-01-08T00:15:47.000Z","updated_at":"2025-05-01T11:43:55.000Z","dependencies_parsed_at":"2023-01-14T11:11:23.243Z","dependency_job_id":null,"html_url":"https://github.com/mattkrick/redux-optimistic-ui","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattkrick%2Fredux-optimistic-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattkrick%2Fredux-optimistic-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattkrick%2Fredux-optimistic-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattkrick%2Fredux-optimistic-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattkrick","download_url":"https://codeload.github.com/mattkrick/redux-optimistic-ui/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254436948,"owners_count":22070947,"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":["optimistic-ui","reducer-enhancer","redux"],"created_at":"2024-11-14T02:19:54.259Z","updated_at":"2025-05-15T23:07:36.800Z","avatar_url":"https://github.com/mattkrick.png","language":"JavaScript","funding_links":[],"categories":["📦 Legacy \u0026 Inactive Projects"],"sub_categories":[],"readme":"[![npm version](https://badge.fury.io/js/redux-optimistic-ui.svg)](https://badge.fury.io/js/redux-optimistic-ui)\n[![Build Status](https://travis-ci.org/mattkrick/redux-optimistic-ui.svg?branch=master)](https://travis-ci.org/mattkrick/redux-optimistic-ui)\n[![Coverage Status](https://coveralls.io/repos/github/mattkrick/redux-optimistic-ui/badge.svg?branch=master)](https://coveralls.io/github/mattkrick/redux-optimistic-ui?branch=master)\n\n# redux-optimistic-ui\na reducer enhancer to enable type-agnostic optimistic updates\n\n## Installation\n`yarn add redux-optimistic-ui`\n\n## A what-now?\nA reducer enhance is a function you put around a reducer.\nIt can be your rootReducer (the output from a `combineReducers`) or a nested one.\nOptimistic-UI means you update what the client sees before the result comes back from the server.\nThis makes your app feel super fast, regardless of server location or internet connection speed.\n\n## How's it different from redux-optimist?\n\n| redux-optimistic-ui                                    | redux-optimist                                                    |\n|--------------------------------------------------------|-------------------------------------------------------------------|\n| reducerEnhancer (wraps your state)                     | reducerExtender (adds an optimist to your state)                  |\n| can use immutable.js or anything else                  | must use plain JS objects for your state                          |\n| only uses 1 state copy                                 | saves an extra copy of your state for every new optimistic action |\n| FSA compliant                                          | not FSA compliant                                                 |\n| must wrap your state calls in `ensureState`            | no change necessary to get your state                             |\n\n## Usage\n\n### Feed it your reducer\n\n```js\nimport {optimistic} from 'redux-optimistic-ui';\nreturn optimistic(reducer);\n```\n\nThis will transform your state so it looks like this:\n\n```js\nstate = {\n  history: [],\n  beforeState: \u003cYOUR PREVIOUS STATE HERE\u003e\n  current: \u003cYOUR STATE HERE\u003e\n}\n```\nIf the client is not waiting for a response from the server, the following are guaranteed to be true:\n- `state.history.length === 0`\n- `state.beforeState === undefined`\n\nIf you don't need to know if there is an outstanding fetch, you'll never need to use these.\n\n### Update your references to `state`\n\nSince your state is now wrapped, you need `state.current`.\nBut that sucks. What if you don't enhance the state until the user hits a certain route?\nLucky you! There's a function for that. `ensureState` will give you your state whether it's enhanced or not.\nJust wrap all your references to `state` and `getState` with it \u0026 you're all set!\n\n```js\n// Before\ngetState().counter\n\n// After (whether you've enhanced your reducer or not)\nimport {ensureState} from 'redux-optimistic-ui'\nensureState(getState()).counter\n```\n\n### Write some middleware\n\nNow comes the fun! Not all of your actions should be optimistic.\nJust the ones that fetch something from a server *and have a high probability of success*.\nI like real-world examples, so this middleware is a little bit longer than the bare requirements:\n\n```js\nimport {BEGIN, COMMIT, REVERT} from 'redux-optimistic-ui';\n\n//All my redux action types that are optimistic have the following suffixes, yours may vary\nconst _SUCCESS = '_SUCCESS';\nconst _ERROR = '_ERROR';\n\n//Each optimistic item will need a transaction Id to internally match the BEGIN to the COMMIT/REVERT\nlet nextTransactionID = 0;\n\n// That crazy redux middleware that's 3 functions deep!\nexport default store =\u003e next =\u003e action =\u003e {\n  // FSA compliant\n  const {type, meta, payload} = action;\n\n  // For actions that have a high probability of failing, I don't set the flag\n  if (!meta || !meta.isOptimistic) return next(action);\n\n  // Now that we know we're optimistically updating the item, give it an ID\n  let transactionID = nextTransactionID++;\n\n  // Extend the action.meta to let it know we're beginning an optimistic update\n  next(Object.assign({}, action, {meta: {optimistic: {type: BEGIN, id: transactionID}}}));\n\n  // HTTP is boring, I like sending data over sockets, the 3rd arg is a callback\n  socket.emit(type, payload, error =\u003e {\n    // Create a redux action based on the result of the callback\n    next({\n      type: type + (error ? _ERROR : _SUCCESS),\n      error,\n      payload,\n      meta: {\n        //Here's the magic: if there was an error, revert the state, otherwise, commit it\n        optimistic: error ? {type: REVERT, id: transactionID} : {type: COMMIT, id: transactionID}\n      }\n    });\n  })\n};\n```\n\n## Pro tips\nNot using an optimistic-ui until a certain route? Using something like `redux-undo` in other parts? Write a little something like this and call it on your asychronous route:\n\n```js\nexport default (newReducers, reducerEnhancers) =\u003e {\n  Object.assign(currentReducers, newReducers);\n  const reducer = combineReducers({...currentReducers})\n  if (reducerEnhancers){\n    return Array.isArray(reducerEnhancers) ? compose(...reducerEnhancers)(reducer) : reducerEnhancers(reducer);\n  }\n  return reducer;\n}\n```\nNow you get an enhanced reducer only where you want it. Neat.\n\nTo see how it all comes together, check out https://github.com/mattkrick/meatier.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattkrick%2Fredux-optimistic-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattkrick%2Fredux-optimistic-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattkrick%2Fredux-optimistic-ui/lists"}