{"id":13450272,"url":"https://github.com/staltz/use-profunctor-state","last_synced_at":"2025-04-05T10:07:29.901Z","repository":{"id":53820806,"uuid":"157857678","full_name":"staltz/use-profunctor-state","owner":"staltz","description":"React Hook for state management with profunctor lenses","archived":false,"fork":false,"pushed_at":"2020-01-13T09:14:37.000Z","size":28,"stargazers_count":333,"open_issues_count":4,"forks_count":11,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-29T09:08:34.399Z","etag":null,"topics":["lenses","profunctors","react","react-hooks"],"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/staltz.png","metadata":{"files":{"readme":"readme.md","changelog":null,"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},"funding":{"patreon":"andrestaltz"}},"created_at":"2018-11-16T11:22:28.000Z","updated_at":"2024-12-17T19:04:46.000Z","dependencies_parsed_at":"2022-08-24T08:11:01.783Z","dependency_job_id":null,"html_url":"https://github.com/staltz/use-profunctor-state","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staltz%2Fuse-profunctor-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staltz%2Fuse-profunctor-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staltz%2Fuse-profunctor-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staltz%2Fuse-profunctor-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/staltz","download_url":"https://codeload.github.com/staltz/use-profunctor-state/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247318744,"owners_count":20919484,"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":["lenses","profunctors","react","react-hooks"],"created_at":"2024-07-31T07:00:33.114Z","updated_at":"2025-04-05T10:07:29.879Z","avatar_url":"https://github.com/staltz.png","language":"JavaScript","funding_links":["https://patreon.com/andrestaltz"],"categories":["Packages"],"sub_categories":[],"readme":"# Profunctor State Hook\n\n*React Hook for state management with Profunctor Optics*\n\nA simple and small (2KB!) approach to state management in React using functional lenses (a type of profunctor optics). A lens is made of two functions: **get** (like selectors in Redux, or computed values in MobX) and **set** (the opposite of a selector, creates new parent state). This way, parent state and child state are kept in sync, updating back and forth automatically.\n\n```\nnpm install --save @staltz/use-profunctor-state\n```\n\nSee also [@staltz/**with**-profunctor-state](https://github.com/staltz/with-profunctor-state).\n\n## Example\n\nSuppose your app handles temperatures in Fahrenheit, but one component works only with Celsius. You can create a conversion layer between those two with `promap(get, set)`.\n\nOpen this also in a [CodeSandbox](https://codesandbox.io/s/3vz5vl5p5).\n\n```js\nfunction App() {\n  const initialState = {fahrenheit: 70, other: {}}\n\n  const appProf = useProfunctorState(initialState);\n  // or:\n  // const {state, setState, promap} = useProfunctorState(initialState);\n\n  const celsiusProf = appProf.promap(\n    state =\u003e fToC(state.fahrenheit),\n    (celsius, state) =\u003e ({ ...state, fahrenheit: cToF(celsius) })\n  );\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eGlobal app state: {JSON.stringify(appProf.state)}\u003c/div\u003e\n      \u003cCelsiusThermometer {...celsiusProf} /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nBecause promap is composable, you can also split the conversion layer into multiple parts:\n\n```js\nconst celsiusProf = appProf\n  .promap(s =\u003e s.fahrenheit, (f, s) =\u003e ({ ...s, fahrenheit: f }))\n  .promap(fToC, cToF);\n```\n\nThe CelsiusThermometer component received props `state`, `setState` and `promap` from the spread of `celsiusProf`:\n\n- `state`: in this case it's a number representing celsius\n- `setState`: does what you think it does!\n- `promap`: use this if CelsiusThermometer would have children components\n\n```js\nfunction CelsiusThermometer({ state, setState, promap }) {\n  const onColder = () =\u003e setState(prev =\u003e prev - 5);\n  const onHotter = () =\u003e setState(prev =\u003e prev + 5);\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={onColder}\u003eColder\u003c/button\u003e\n      \u003cbutton onClick={onHotter}\u003eHotter\u003c/button\u003e\n      \u003cThermometer value={state} max=\"100\" steps=\"4\" format=\"°C\" /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## Benefits\n\n#### Simpler architecture\n\n- Global app state == Props == Local component state\n- No actions, no reducers, no dispatch, no store\n- Selector/unselector conversion layers in the component tree\n\n#### Familiar\n\n- `state` and `setState` work just like you would assume\n- Easy to migrate your apps to use profunctors\n\n#### Fractal\n\n- Build the parent components like you build the smaller components\n- Same pattern applies to all components, both Presentational and Container\n\n#### Decoupling\n\n- Every child component assumes nothing about its parent component\n- Child components with props `{state, setState, promap}` can be published as-is to NPM\n\n#### Functional\n\n- Lenses are composable and operate immutably, just like Redux selectors\n- Chain `.promap` calls like you would chain `.map` calls\n- Backed by [mathematical theory](https://github.com/hablapps/DontFearTheProfunctorOptics/)\n\n#### Performance similar to Redux\n\n- Sprinkle `React.memo()` here and there to avoid full-app rerenders\n\n#### Small: 2 KB and 80 lines of code\n\n#### TypeScript support\n\n## Downsides\n\nCompared to Redux and similar (ngrx, Vuex):\n\n- No actions means no support for Redux DevTools\n- This library itself is not used in production yet\n\n## API\n\n#### `useProfunctorState(initial, [args])`\n\n```js\nconst {state, setState, promap} = useProfunctorState(initial);\n```\n\nReact hook that should be called in the body of a function component. Returns a profunctor state object, which consists of three parts:\n\n- `state`: the data, initially this will be `initial`\n- `setState`: works just like React's traditional setState\n  - `setState(newState)` or\n  - `setState(prev =\u003e ...)`\n- `promap(get, set)`: creates a new profunctor state object based on the current one, given two functions:\n  - `get: parentState =\u003e childState`\n  - `set: (newChild, oldParent) =\u003e newParent`\n\nPromap also alternatively supports a lens object, which is simply `promap({get, set})` instead of `promap(get, set)`. This is useful in case you want to publish a lens object elsewhere and simply pass it into the promap.\n\nUnderneath, this hook uses `useState` and `useMemo`. The second argument (optional) `[args]` is an array of inputs that dictates when to recompute the memoized profunctor state object, just like with `useMemo(_, args)`. By default, the args array is `[state]`.\n\n#### `withProfunctorState(ProComponent, initial, [args])`\n\nHigher-order component that does the same as `useProfunctorState`, but accepts as input a Pro Component (component that wants props `state`, `setState`, `promap`), and returns a new component that calls `useProfunctorState` internally.\n\n## Pro Components\n\nA Pro Component is any component that expects all or some of these props `{state, setState, promap}`. When you use this library, you will begin writing Pro Components to consume pieces of the global app state. For instance, in the example above, the CelsiusThermometer was a Pro Component:\n\n```js\nfunction CelsiusThermometer({ state, setState, promap }) {\n  const onColder = () =\u003e setState(prev =\u003e prev - 5);\n  const onHotter = () =\u003e setState(prev =\u003e prev + 5);\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={onColder}\u003eColder\u003c/button\u003e\n      \u003cbutton onClick={onHotter}\u003eHotter\u003c/button\u003e\n      \u003cThermometer value={state} max=\"100\" steps=\"4\" format=\"°C\" /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nA Pro Component can put its local state in the `state` prop using `setState`. You can also think of this `setState` as `setProps`. Writing components in this style is familiar, because `setState` works just like the traditional API. But now we have the added benefit that Pro Components can be published as-is (they are just functions!) to NPM, and there is no need to import `@staltz/use-profunctor-state` as a dependency of a Pro Component. This way you get encapsulated and composable pieces of state management that can be shared across applications. Pro Components can either be presentational or logic-heavy container components.\n\nPS: it might be good to apply `React.memo()` on every Pro Component by default.\n\n## FAQ\n\n#### What about performance?\n\nBy default, each child's `setState` will cause a top-level state update which will rerender the entire hierarchy below. This is a bad thing, but it's not unlike Redux, where you need to carefully design `shouldComponentUpdate`. With profunctor state, just add `React.memo` to a Pro Component and that should do the same as `shouldComponentUpdate`, the memo will shallow compare the props (i.e. `state`, `setState`, `promap`, although only `state` is interesting during updates).\n\nCheck [this CodeSandbox](https://codesandbox.io/s/l5w1rpnv17) with `React.memo` usage, where background colors change upon re-render.\n\n#### Can I still have truly internal local state?\n\nYes. Nothing stops you from adding a `useState` hook so you can have truly internal state in a Pro Component, such as:\n\n```diff\n function CelsiusThermometer({ state, setState, promap }) {\n   const onColder = () =\u003e setState(prev =\u003e prev - 5);\n   const onHotter = () =\u003e setState(prev =\u003e prev + 5);\n+  const [steps, setSteps] = useState(4);\n   return (\n     \u003cdiv\u003e\n       \u003cbutton onClick={onColder}\u003eColder\u003c/button\u003e\n       \u003cbutton onClick={onHotter}\u003eHotter\u003c/button\u003e\n-      \u003cThermometer value={state} max=\"100\" steps=\"4\" format=\"°C\" /\u003e\n+      \u003cThermometer value={state} max=\"100\" steps={steps} format=\"°C\" /\u003e\n     \u003c/div\u003e\n   );\n }\n```\n\n#### Is this production-ready?\n\nTheoretically, yes, it was designed after [Cycle State](https://cycle.js.org/api/state.html). The community has been using functional lenses in Cycle State (a.k.a. [cycle-onionify](https://github.com/staltz/cycle-onionify/)) for at least a year, also in production. Lenses are also not new, they're in JS libraries like [Ramda](http://ramdajs.com/) and [Partial Lenses](https://github.com/calmm-js/partial.lenses), but much more common in functional languages like Haskell.\n\nIn practice, this specific library has not been used in production (neither have React hooks!), so you shouldn't go right ahead and convert any app to this style. That said, this was a conference-driven developed library, just like Redux was.\n\n#### Why `@staltz/use-profunctor-state` and not `use-profunctor-state`?\n\nFirst, I want to wait til React hooks are official. Second, I don't want to pollute the NPM registry. Third, I believe most people should author packages under their own scope (just like in GitHub!), so that forks can indicate who is maintaining the package, because I don't intend to maintain this package, although it's small and might not even need maintenance.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaltz%2Fuse-profunctor-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstaltz%2Fuse-profunctor-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaltz%2Fuse-profunctor-state/lists"}