{"id":13450285,"url":"https://github.com/diegohaz/constate","last_synced_at":"2025-06-10T23:35:02.335Z","repository":{"id":31301195,"uuid":"127461319","full_name":"diegohaz/constate","owner":"diegohaz","description":"React Context + State","archived":false,"fork":false,"pushed_at":"2025-04-01T11:27:24.000Z","size":2017,"stargazers_count":3985,"open_issues_count":9,"forks_count":90,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-06-01T16:19:26.095Z","etag":null,"topics":["constate","hooks","react","react-context","react-hooks","react-state","reactjs","reakit","state-management"],"latest_commit_sha":null,"homepage":"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/counter","language":"TypeScript","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/diegohaz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/funding.yml","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,"zenodo":null},"funding":{"github":"diegohaz"}},"created_at":"2018-03-30T18:40:18.000Z","updated_at":"2025-05-25T05:33:43.000Z","dependencies_parsed_at":"2025-04-28T11:59:37.402Z","dependency_job_id":"1a85f743-9b44-4aed-afe2-9858317ba38c","html_url":"https://github.com/diegohaz/constate","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegohaz%2Fconstate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegohaz%2Fconstate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegohaz%2Fconstate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegohaz%2Fconstate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/diegohaz","download_url":"https://codeload.github.com/diegohaz/constate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegohaz%2Fconstate/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259171395,"owners_count":22816470,"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":["constate","hooks","react","react-context","react-hooks","react-state","reactjs","reakit","state-management"],"created_at":"2024-07-31T07:00:33.302Z","updated_at":"2025-06-10T23:35:02.288Z","avatar_url":"https://github.com/diegohaz.png","language":"TypeScript","funding_links":["https://github.com/sponsors/diegohaz"],"categories":["Packages","TypeScript","react","React","Frontend  frameworks \u0026 libraries","Components","Frameworks","List","Libraries"],"sub_categories":["Data","State of the React"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/diegohaz/constate/master/logo/logo.png\" alt=\"constate logo\" width=\"300\" /\u003e\n\u003c/p\u003e\n\n# Constate\n\n\u003ca href=\"https://npmjs.org/package/constate\"\u003e\u003cimg alt=\"NPM version\" src=\"https://img.shields.io/npm/v/constate.svg?style=flat-square\"\u003e\u003c/a\u003e\n\u003ca href=\"https://npmjs.org/package/constate\"\u003e\u003cimg alt=\"NPM downloads\" src=\"https://img.shields.io/npm/dm/constate.svg?style=flat-square\"\u003e\u003c/a\u003e\n\u003ca href=\"https://unpkg.com/constate\"\u003e\u003cimg alt=\"Size\" src=\"https://img.badgesize.io/https://unpkg.com/constate?style=flat-square\"\u003e\u003c/a\u003e\n\u003ca href=\"https://david-dm.org/diegohaz/constate\"\u003e\u003cimg alt=\"Dependencies\" src=\"https://img.shields.io/david/diegohaz/constate.svg?style=flat-square\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/diegohaz/constate/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"GitHub Workflow Status (branch)\" src=\"https://img.shields.io/github/workflow/status/diegohaz/constate/ci/master\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/diegohaz/constate/branch/master\"\u003e\u003cimg alt=\"Coverage Status\" src=\"https://img.shields.io/codecov/c/github/diegohaz/constate/master.svg?style=flat-square\"\u003e\u003c/a\u003e\n\nWrite local state using [React Hooks](https://reactjs.org/docs/hooks-intro.html) and lift it up to [React Context](https://reactjs.org/docs/context.html) only when needed with minimum effort.\n\n\u003cbr\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth colspan=\"5\"\u003e\u003ccenter\u003e🕹 CodeSandbox demos 🕹\u003c/center\u003e\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e\u003ca href=\"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/counter?module=/App.js\"\u003eCounter\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/i18n?module=/App.js\"\u003eI18n\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/theming?module=/App.js\"\u003eTheming\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/typescript?module=/App.tsx\"\u003eTypeScript\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/wizard-form?module=/App.js\"\u003eWizard Form\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003cbr\u003e\n\n## Basic example\n\n```jsx\nimport React, { useState } from \"react\";\nimport constate from \"constate\";\n\n// 1️⃣ Create a custom hook as usual\nfunction useCounter() {\n  const [count, setCount] = useState(0);\n  const increment = () =\u003e setCount(prevCount =\u003e prevCount + 1);\n  return { count, increment };\n}\n\n// 2️⃣ Wrap your hook with the constate factory\nconst [CounterProvider, useCounterContext] = constate(useCounter);\n\nfunction Button() {\n  // 3️⃣ Use context instead of custom hook\n  const { increment } = useCounterContext();\n  return \u003cbutton onClick={increment}\u003e+\u003c/button\u003e;\n}\n\nfunction Count() {\n  // 4️⃣ Use context in other components\n  const { count } = useCounterContext();\n  return \u003cspan\u003e{count}\u003c/span\u003e;\n}\n\nfunction App() {\n  // 5️⃣ Wrap your components with Provider\n  return (\n    \u003cCounterProvider\u003e\n      \u003cCount /\u003e\n      \u003cButton /\u003e\n    \u003c/CounterProvider\u003e\n  );\n}\n```\n\n[Learn more](#api)\n\n## Advanced example\n\n```jsx\nimport React, { useState, useCallback } from \"react\";\nimport constate from \"constate\";\n\n// 1️⃣ Create a custom hook that receives props\nfunction useCounter({ initialCount = 0 }) {\n  const [count, setCount] = useState(initialCount);\n  // 2️⃣ Wrap your updaters with useCallback or use dispatch from useReducer\n  const increment = useCallback(() =\u003e setCount(prev =\u003e prev + 1), []);\n  return { count, increment };\n}\n\n// 3️⃣ Wrap your hook with the constate factory splitting the values\nconst [CounterProvider, useCount, useIncrement] = constate(\n  useCounter,\n  value =\u003e value.count, // becomes useCount\n  value =\u003e value.increment // becomes useIncrement\n);\n\nfunction Button() {\n  // 4️⃣ Use the updater context that will never trigger a re-render\n  const increment = useIncrement();\n  return \u003cbutton onClick={increment}\u003e+\u003c/button\u003e;\n}\n\nfunction Count() {\n  // 5️⃣ Use the state context in other components\n  const count = useCount();\n  return \u003cspan\u003e{count}\u003c/span\u003e;\n}\n\nfunction App() {\n  // 6️⃣ Wrap your components with Provider passing props to your hook\n  return (\n    \u003cCounterProvider initialCount={10}\u003e\n      \u003cCount /\u003e\n      \u003cButton /\u003e\n    \u003c/CounterProvider\u003e\n  );\n}\n```\n\n[Learn more](#splitvalues)\n\n## Installation\n\nnpm:\n\n```sh\nnpm i constate\n```\n\nYarn:\n\n```sh\nyarn add constate\n```\n\n## API\n\n### `constate(useValue[, ...selectors])`\n\nConstate exports a single factory method. As parameters, it receives [`useValue`](#usevalue) and optional [`selector`](#selectors) functions. It returns a tuple of `[Provider, ...hooks]`.\n\n#### `useValue`\n\nIt's any [custom hook](https://reactjs.org/docs/hooks-custom.html):\n\n```js\nimport { useState } from \"react\";\nimport constate from \"constate\";\n\nconst [CountProvider, useCountContext] = constate(() =\u003e {\n  const [count] = useState(0);\n  return count;\n});\n```\n\nYou can receive props in the custom hook function. They will be populated with `\u003cProvider /\u003e`:\n\n```jsx\nconst [CountProvider, useCountContext] = constate(({ initialCount = 0 }) =\u003e {\n  const [count] = useState(initialCount);\n  return count;\n});\n\nfunction App() {\n  return (\n    \u003cCountProvider initialCount={10}\u003e\n      ...\n    \u003c/CountProvider\u003e\n  );\n}\n```\n\nThe API of the containerized hook returns the same value(s) as the original, as long as it is a descendant of the Provider:\n\n```jsx\nfunction Count() {\n  const count = useCountContext();\n  console.log(count); // 10\n}\n```\n\n#### `selectors`\n\nOptionally, you can pass in one or more functions to split the custom hook value into multiple React Contexts. This is useful so you can avoid unnecessary re-renders on components that only depend on a part of the state.\n\nA `selector` function receives the value returned by [`useValue`](#usevalue) and returns the value that will be held by that particular Context.\n\n```jsx\nimport React, { useState, useCallback } from \"react\";\nimport constate from \"constate\";\n\nfunction useCounter() {\n  const [count, setCount] = useState(0);\n  // increment's reference identity will never change\n  const increment = useCallback(() =\u003e setCount(prev =\u003e prev + 1), []);\n  return { count, increment };\n}\n\nconst [Provider, useCount, useIncrement] = constate(\n  useCounter,\n  value =\u003e value.count, // becomes useCount\n  value =\u003e value.increment // becomes useIncrement\n);\n\nfunction Button() {\n  // since increment never changes, this will never trigger a re-render\n  const increment = useIncrement();\n  return \u003cbutton onClick={increment}\u003e+\u003c/button\u003e;\n}\n\nfunction Count() {\n  const count = useCount();\n  return \u003cspan\u003e{count}\u003c/span\u003e;\n}\n```\n\n## Contributing\n\nIf you find a bug, please [create an issue](https://github.com/diegohaz/constate/issues/new) providing instructions to reproduce it. It's always very appreciable if you find the time to fix it. In this case, please [submit a PR](https://github.com/diegohaz/constate/pulls).\n\nIf you're a beginner, it'll be a pleasure to help you contribute. You can start by reading [the beginner's guide to contributing to a GitHub project](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/).\n\nWhen working on this codebase, please use `yarn`. Run `yarn examples` to run examples.\n\n## License\n\nMIT © [Diego Haz](https://github.com/diegohaz)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegohaz%2Fconstate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiegohaz%2Fconstate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegohaz%2Fconstate/lists"}