{"id":29926609,"url":"https://github.com/ecomfe/use-optimistic","last_synced_at":"2025-08-02T12:42:39.131Z","repository":{"id":35057103,"uuid":"201597398","full_name":"ecomfe/use-optimistic","owner":"ecomfe","description":"React hooks to help manage optimistic states","archived":false,"fork":false,"pushed_at":"2022-12-11T01:28:41.000Z","size":3228,"stargazers_count":8,"open_issues_count":24,"forks_count":1,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-07-06T15:49:31.576Z","etag":null,"topics":[],"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/ecomfe.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-08-10T07:42:10.000Z","updated_at":"2024-07-09T03:37:17.000Z","dependencies_parsed_at":"2023-01-15T13:00:23.149Z","dependency_job_id":null,"html_url":"https://github.com/ecomfe/use-optimistic","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ecomfe/use-optimistic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fuse-optimistic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fuse-optimistic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fuse-optimistic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fuse-optimistic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecomfe","download_url":"https://codeload.github.com/ecomfe/use-optimistic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fuse-optimistic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268392180,"owners_count":24243297,"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","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-08-02T12:42:17.168Z","updated_at":"2025-08-02T12:42:39.113Z","avatar_url":"https://github.com/ecomfe.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# use-optimistic\n\nThis is a set of react hooks to help manage optimistic states.\n\nAs previously stated in [redux-optimistic-thunk](https://github.com/ecomfe/redux-optimistic-thunk#why-this-middleware), manually managing optimistic states, commits, rollbacks and transactions are not ideal model of state management. React hooks provides powers to manage states in a more functional way, and this library aimed to build optimistic functions above hooks.\n\n**This library required [ES6 Generators](https://caniuse.com/#feat=es6-generators) to work.**\n\n## Usage\n\n### Install\n\n```shell\nnpm install use-optimistic\n```\n\nThis library provides 3 hooks to developers.\n\n### useOptimisticFactory\n\nThis is the fundamental hooks which manages a full functional optimistic state:\n\n```js\nconst [state, dispatch] = useOptimisticFactory(factory, initialState);\n```\n\nThe `factory` parameter referes to a function receiving a `payload` object and returns either:\n\n- A state reducer `(state: T) =\u003e T`, this reducer will be executed immediately providing current state, the returned state is going to be the next state.\n- A tuple of `[asyncWorkflow, optimisticReducer]` which defines an async workflow and a optimistic reducer to take place before async operations complete.\n\nAn `asyncWorkflow` is a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) which yields a reducer or a `Promise` instance.\n\nAny time a state reducer is yielded, it will be executed against current state and generates the next state.\n\nWhen a `Promise` is yielded, it will be awaited, the resolved value will be returned to `yield` statement.\n\nRight after the first `Promise` is yielded, the `optimisticReducer` will be executed to generate an optimistic state, inside `useOptimisticFactory` hook it will automatically rollback this optimistic state after `Promise` is settled (either fulfilled or rejected).\n\nNote that `optimisticReducer` will be only executed on the first `Promise`, so if `asyncWorkflow` yields several `Promise`s the later ones will not take benefit from optimistic state.\n\nThe return value of `useOptimisticFactory` is the same signature of `useReducer`, the `state` represents the latest state and `dispatch` is a function to feed `payload` to `factory` argument.\n\nOne thing to metion is that `dispatch` will be different if `factory` changes, this is different to the built-in `useReducer` hook, we recommend to cache `factory` with `useCallback`.\n\nThis is a simple example to manage a todo list with `useOptimisticFactory`:\n\n```js\nconst factory = useCallback(\n    ({type, payload}) =\u003e {\n        switch (type) {\n            case 'DELETE':\n                return items =\u003e {\n                    const index = items.findIndex(i =\u003e i.id === payload);\n                    return [\n                        ...items.slice(0, index),\n                        {...items[index], deleted: true},\n                        ...items.slice(index + 1),\n                    ];\n                };\n            case 'CREATE':\n                return [\n                    function* create() {\n                        // Await an async api call\n                        const newTodo = yield saveTodo(payload);\n                        // Insert the returned new todo to list, with pending set to false\n                        yield items =\u003e [\n                            ...items,\n                            {...newTodo, pending: false, deleted: false},\n                        ];\n                    },\n                    items =\u003e [\n                        ...items,\n                        // Insert an optimistic item with property pending set to true,\n                        // this item will be removed after saveTodo resolves\n                        {id: uid(), text: payload, pending: true, deleted: false},\n                    ],\n                ];\n            default:\n                return s =\u003e s;\n        }\n    },\n    []\n);\nconst [todos, dispatch] = useOptimisticFactory(factory, []);\n```\n\nYou can call `dispatch` at any time, parallelism is handled internally. See [demo](demo/components/App/index.js) to find more details.\n\n### useOptimisticState\n\nLike `useState` and `useReducer`, `useOptimisticState` is a simmple encapsulation to `useOptimisticFactory`.\n\n```js\nconst [state, setState] = useOptimisticState(initialState);\n```\n\nThe `setState` can receive 2 different arguments:\n\n```js\nsetState(nextState);\nsetState(promise, optimisticNextState);\n```\n\nIf only 1 argument is provided, this works exactly the same as `useState` hook, `nextState` can be either a state object or a state reducer `(state: T) =\u003e T`.\n\nWhen 2 arguments are provided, the first one is a `Promise` which resolves to a `nextState` (which is a state object or a reducer), the second is a `nextState` takes optimistic effects.\n\n```js\nconst [todos, setTodos] = useOptimisticState([]);\nconst addTodo = todo =\u003e setState(\n    (async () =\u003e {\n        const newTodo = await saveTodo(todo);\n        // We recommend to use a reducer since it is asynchronous\n        return todos =\u003e [...todos, {...newTodo, pending: false, deleted: false}];\n    })(),\n    // Optimistic next state is executed synchronously, it can be a single state object\n    [...todos, {...todo, pending: true, deleted: false}]\n);\n```\n\n### useOptimisticTask\n\nThis is a binding of `useOptimisticState` and an async task.\n\n```js\nconst [state, run] = useOptimisticTask(task, optimisticTask);\n```\n\n- The `task` is an async function `(arg: TArg) =\u003e Promise\u003cTState\u003e`.\n- The `optimisticTask` is a sync version of task provides an optimistic response `(arg: TArg) =\u003e TState`.\n- Returned `run` function receives the same argument as `task`.\n\n```js\nconst newTodo = async todo =\u003e {\n    const newTodo = await saveTodo(todo);\n    // We recommend to use a reducer since it is asynchronous\n    return todos =\u003e [...todos, {...newTodo, pending: false, deleted: false}];\n};\nconst optimisticNewTodo = todo =\u003e todos =\u003e [...todos, {...todo, pending: true, deleted: false}];\nconst [todos, addTodo] = useOptimisticTask(newTodo, optimisticNewTodo, []);\n```\n\n`useOptimisticTask` is useful when encapsulating business aware hooks.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Fuse-optimistic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecomfe%2Fuse-optimistic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Fuse-optimistic/lists"}