{"id":18563273,"url":"https://github.com/compulim/use-state-with-ref","last_synced_at":"2025-12-24T09:35:49.021Z","repository":{"id":196285329,"uuid":"695689908","full_name":"compulim/use-state-with-ref","owner":"compulim","description":"React useState hook with readonly RefObject","archived":false,"fork":false,"pushed_at":"2025-02-12T10:31:37.000Z","size":641,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-23T11:41:36.973Z","etag":null,"topics":["hooks","react","react-hooks","use-state"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/use-state-with-ref/","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/compulim.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-23T22:53:19.000Z","updated_at":"2025-04-17T18:24:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"b8591a0a-271f-4ad4-bcb9-dce63666075d","html_url":"https://github.com/compulim/use-state-with-ref","commit_stats":null,"previous_names":["compulim/use-state-with-ref"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compulim%2Fuse-state-with-ref","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compulim%2Fuse-state-with-ref/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compulim%2Fuse-state-with-ref/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compulim%2Fuse-state-with-ref/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/compulim","download_url":"https://codeload.github.com/compulim/use-state-with-ref/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254394726,"owners_count":22063984,"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":["hooks","react","react-hooks","use-state"],"created_at":"2024-11-06T22:12:13.365Z","updated_at":"2025-12-24T09:35:48.935Z","avatar_url":"https://github.com/compulim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `use-state-with-ref`\n\nReact `useState` with a readonly `RefObject`.\n\n## Background\n\nComponents often detect changes of props to check if the component need to be re-rendered. If the function is changed, the component should be re-rendered. To optimize performance, unnecessary changes should be removed.\n\nFor example, unless there is an intentional change, the return value of `useCallback` should be kept the same across the lifetime of the component.\n\n`useStateWithRef` call `useState` and appending a readonly `RefObject`. The `RefObject` can be used in `useCallback` to minimize function changes.\n\n## How to use\n\n### Before optimization\n\n```tsx\nconst MyComponent = ({ onSubmit }) =\u003e {\n  const [value, setValue] = useState();\n\n  // This callback will be recreated every time \u003cMyComponent\u003e is rendered.\n  const handleInput = ({ currentTarget: { value } }) =\u003e setValue(value);\n\n  // This callback will be recreated every time \u003cMyComponent\u003e is rendered.\n  const handleSubmit = () =\u003e onSubmit.current(valueRef.current);\n\n  return (\n    // \u003cform\u003e will be re-rendered every time \u003cMyComponent\u003e is rendered.\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003cinput onInput={handleInput} type=\"text\" value={value} /\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n### Optimization without `useStateWithRef`\n\n```tsx\nconst MyComponent = ({ onSubmit }) =\u003e {\n  const [value, setValue] = useState();\n  const onSubmitRef = useRefFrom(onSubmit);\n  const valueRef = useRef();\n\n  valueRef.current = value;\n\n  // This callback will never change across the lifetime of \u003cMyComponent\u003e because `setValue` never change.\n  const handleInput = useCallback(({ currentTarget: { value } }) =\u003e setValue(value), [setValue]);\n\n  // This callback will never change across the lifetime of \u003cMyComponent\u003e because `onSubmitRef` and `valueRef` never change.\n  const handleSubmit = useCallback(() =\u003e onSubmitRef.current(valueRef.current), [onSubmitRef, valueRef]);\n\n  return (\n    // \u003cform\u003e will never re-render across the lifetime of \u003cMyComponent\u003e.\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003cinput onInput={handleInput} type=\"text\" value={value} /\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n### Optimization with `useStateWithRef`\n\n```tsx\nconst MyComponent = ({ onSubmit }) =\u003e {\n  const [value, setValue, valueRef] = useStateWithRef();\n  const onSubmitRef = useRefFrom(onSubmit);\n\n  // This callback will never change across the lifetime of \u003cMyComponent\u003e because `setValue` never change.\n  const handleInput = useCallback(({ currentTarget: { value } }) =\u003e setValue(value), [setValue]);\n\n  // This callback will never change across the lifetime of \u003cMyComponent\u003e because `onSubmitRef` and `valueRef` never change.\n  const handleSubmit = useCallback(() =\u003e onSubmitRef.current(valueRef.current), [onSubmitRef, valueRef]);\n\n  return (\n    // \u003cform\u003e will never re-render across the lifetime of \u003cMyComponent\u003e.\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003cinput onInput={handleInput} type=\"text\" value={value} /\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n## API\n\n```ts\nexport default function useStateWithRef\u003cS\u003e(\n  initialState: S | (() =\u003e S)\n): [\n  S,\n  Dispatch\u003cSetStateAction\u003cS\u003e\u003e,\n  ReadonlyRefObject\u003cS\u003e\n];\n\nexport default function useStateWithRef\u003cS = undefined\u003e(): [\n  S | undefined,\n  Dispatch\u003cSetStateAction\u003cS | undefined\u003e\u003e,\n  ReadonlyRefObject\u003cS | undefined\u003e\n];\n```\n\n## Behaviors\n\n### Why should I use `RefObject` to optimize performance?\n\nWhen a prop change, a component will need to be re-rendered. This propagation amplifies when passing unchanged props to a large component. Thus, [`memo()`](https://react.dev/reference/react/memo) (a.k.a. [pure component](https://react.dev/reference/react/PureComponent)) helps prevent rendering when no props changed.\n\n[Pure components](https://react.dev/learn/keeping-components-pure) are components which will only re-render when there is any props changed.\n\nGenerally, when any `onXXX` callback prop is changed, in most cases, the component should not need to be re-rendered because the callback props may not cause a visual change. In other words, changing `onXXX` will re-render but there will be no visual change (a.k.a. a wasted render).\n\nAs props are changed, `memo()` and `PureComponent` could not prevent the wasted render.\n\nDespite `arePropsEqual` and `shouldComponentUpdate` can be used to ignore `onXXX` changes, a bad implementation could easily cause UI issues. Also, some callback functions may have legitimate reasons to cause a re-render. For example, in Fluent UI, changing the `IRenderFunction` should re-render because rows of a `\u003cDetailsList\u003e` could be rendered differently. Thus, `arePropsEqual` and `shouldComponentUpdate` are not generally recommended to use unless in very limited case.\n\nThe most effective way to prevent wasted render is to follow [React best practices on pure rendering logic](https://react.dev/learn/keeping-components-pure), make sure all props are immutable and only change them when there is a visual change.\n\n#### Seriously?\n\nLet's look at [`useSyncExternalStore()`](https://react.dev/reference/react/useSyncExternalStore). When the `subscribe()` callback is changed, the `useSyncExternalStore()` hook will re-subscribe. Callback functions should be properly memoized and only change when needed. Otherwise, in `useSyncExternalStore()` case, every re-render will resubscribe again.\n\n## Contributions\n\nLike us? [Star](https://github.com/compulim/use-state-with-ref/stargazers) us.\n\nWant to make it better? [File](https://github.com/compulim/use-state-with-ref/issues) us an issue.\n\nDon't like something you see? [Submit](https://github.com/compulim/use-state-with-ref/pulls) a pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompulim%2Fuse-state-with-ref","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcompulim%2Fuse-state-with-ref","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompulim%2Fuse-state-with-ref/lists"}