├── .github └── FUNDING.yml ├── .npmignore ├── .gitignore ├── tsconfig.json ├── react.d.ts ├── package.json ├── LICENSE ├── index.ts ├── test.js └── readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: andrestaltz 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.vscode 3 | package-lock.json 4 | shrinkwrap.yaml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.vscode 3 | /index.d.ts 4 | /index.js 5 | /index.js.map 6 | package-lock.json 7 | shrinkwrap.yaml 8 | pnpm-lock.yaml 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "outDir": "./", 8 | "strict": true 9 | }, 10 | "files": ["react.d.ts", "index.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /react.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react' { 2 | export type Updater = (prev: T) => T; 3 | export type SetState = (updater: Updater) => void; 4 | export function useState(initial: T): [T, SetState]; 5 | export function useMemo(factory: () => T, args: Array): T; 6 | export function createElement(comp: any, props: any): any; 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@staltz/use-profunctor-state", 3 | "version": "1.3.0", 4 | "description": "React Hook for state management with profunctor lenses", 5 | "main": "index.js", 6 | "typings": "index.d.ts", 7 | "scripts": { 8 | "compile": "tsc", 9 | "test": "tape test.js", 10 | "prepublishOnly": "npm run compile" 11 | }, 12 | "author": "Andre Staltz ", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/staltz/use-profunctor-state.git" 17 | }, 18 | "peerDependencies": { 19 | "react": ">=16.8.0", 20 | "react-dom": ">=16.8.0" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^10.12.0", 27 | "react": "16.8.6", 28 | "react-dom": "16.8.6", 29 | "react-test-renderer": "16.8.6", 30 | "tape": "^4.8.0", 31 | "typescript": "3.1.x" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 André Staltz (staltz.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import {useState, useMemo, createElement} from 'react'; 2 | 3 | export type Updater = (prev: T) => T; 4 | export type SetState = (updater: Updater) => void; 5 | export type Lens = {get: Getter; set: Setter}; 6 | export type Getter = (outer: T) => S; 7 | export type Setter = (newInner: S, prevOuter: T) => T; 8 | 9 | interface Promap { 10 | (lens: Lens, args?: any[]): ProfunctorState; 11 | (get: Getter, set: Setter, args?: any[]): ProfunctorState; 12 | } 13 | 14 | export class ProfunctorState { 15 | constructor(public state: T, public setState: SetState) {} 16 | promap: Promap = ( 17 | a: Getter | Lens, 18 | b?: Setter | any[], 19 | c?: any[], 20 | ): ProfunctorState => { 21 | const get = typeof a === 'object' ? a.get : a; 22 | const set = typeof a === 'object' ? a.set : (b as Setter); 23 | const args = typeof a === 'object' ? (b as any[]) : c; 24 | const innerSetState: SetState = ( 25 | newInnerStateOrUpdate: S | Updater, 26 | ) => { 27 | this.setState(prevState => { 28 | const innerState = get(prevState); 29 | const newInnerState = 30 | typeof newInnerStateOrUpdate === 'function' 31 | ? (newInnerStateOrUpdate as Updater)(innerState) 32 | : (newInnerStateOrUpdate as S); 33 | if (newInnerState === innerState) return prevState; 34 | return set(newInnerState, prevState); 35 | }); 36 | }; 37 | const innerState = get(this.state); 38 | return useMemoizedProfunctorState(innerState, innerSetState, args); 39 | } 40 | } 41 | 42 | function useMemoizedProfunctorState( 43 | state: T, 44 | setState: SetState, 45 | args?: any[], 46 | ) { 47 | return useMemo( 48 | () => new ProfunctorState(state, setState), 49 | args ? args : [state], 50 | ); 51 | } 52 | 53 | export function useProfunctorState( 54 | initial: T, 55 | args?: any[], 56 | ): ProfunctorState { 57 | const [state, setState] = useState(initial); 58 | return useMemoizedProfunctorState(state, setState, args); 59 | } 60 | 61 | export function withProfunctorState( 62 | Component: any, 63 | initial: T, 64 | args?: any[], 65 | ) { 66 | function WPS() { 67 | const prof = useProfunctorState(initial, args); 68 | return createElement(Component, prof); 69 | } 70 | WPS.displayName = 71 | 'WithProfunctorState(' + 72 | (Component.displayName || Component.name || 'Component') + 73 | ')'; 74 | return WPS; 75 | } 76 | 77 | export default useProfunctorState; 78 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const React = require('react'); 3 | const {default: useProfunctorState} = require('./index'); 4 | const TestRenderer = require('react-test-renderer'); 5 | 6 | test('updates component state', t => { 7 | t.plan(6); 8 | function Input({callbag}) { 9 | const my = useProfunctorState({age: 20}); 10 | React.useEffect(() => { 11 | callbag(0, (t, d) => { 12 | if (t === 1) my.setState(() => ({age: d})); 13 | }); 14 | () => { 15 | callbag(2); 16 | }; 17 | }, []); 18 | return React.createElement('span', null, `My age is ${my.state.age}`); 19 | } 20 | 21 | const makeCallbag = () => { 22 | let talkback; 23 | let value; 24 | return function callbag(t, d) { 25 | if (t === 0 && value) (talkback = d)(1, value); 26 | else if (t === 0) talkback = d; 27 | else if (t === 1 && talkback) talkback(1, (value = d)); 28 | else if (t === 1) value = d; 29 | else if (t === 2) (talkback = undefined), (value = undefined); 30 | }; 31 | }; 32 | 33 | const callbag = makeCallbag(); 34 | const elem = React.createElement(Input, {callbag}); 35 | const testRenderer = TestRenderer.create(elem); 36 | 37 | const result1 = testRenderer.toJSON(); 38 | t.ok(result1, 'should have rendered'); 39 | t.equal(result1.children.length, 1, 'should have one child'); 40 | t.equal(result1.children[0], 'My age is 20', 'should show 20'); 41 | 42 | callbag(1, 30); 43 | testRenderer.update(elem); 44 | 45 | const result2 = testRenderer.toJSON(); 46 | t.ok(result2, 'should have rendered'); 47 | t.equal(result2.children.length, 1, 'should have one child'); 48 | t.equal(result2.children[0], 'My age is 30', 'should show 30'); 49 | 50 | testRenderer.unmount(); 51 | t.end(); 52 | }); 53 | 54 | test('profunctor identity', t => { 55 | t.plan(6); 56 | function Input({callbag}) { 57 | const my = useProfunctorState({age: 20}).promap(x => x, x => x); 58 | React.useEffect(() => { 59 | callbag(0, (t, d) => { 60 | if (t === 1) my.setState(() => ({age: d})); 61 | }); 62 | () => { 63 | callbag(2); 64 | }; 65 | }, []); 66 | return React.createElement('span', null, `My age is ${my.state.age}`); 67 | } 68 | 69 | const makeCallbag = () => { 70 | let talkback; 71 | let value; 72 | return function callbag(t, d) { 73 | if (t === 0 && value) (talkback = d)(1, value); 74 | else if (t === 0) talkback = d; 75 | else if (t === 1 && talkback) talkback(1, (value = d)); 76 | else if (t === 1) value = d; 77 | else if (t === 2) (talkback = undefined), (value = undefined); 78 | }; 79 | }; 80 | 81 | const callbag = makeCallbag(); 82 | const elem = React.createElement(Input, {callbag}); 83 | const testRenderer = TestRenderer.create(elem); 84 | 85 | const result1 = testRenderer.toJSON(); 86 | t.ok(result1, 'should have rendered'); 87 | t.equal(result1.children.length, 1, 'should have one child'); 88 | t.equal(result1.children[0], 'My age is 20', 'should show 20'); 89 | 90 | callbag(1, 30); 91 | testRenderer.update(elem); 92 | 93 | const result2 = testRenderer.toJSON(); 94 | t.ok(result2, 'should have rendered'); 95 | t.equal(result2.children.length, 1, 'should have one child'); 96 | t.equal(result2.children[0], 'My age is 30', 'should show 30'); 97 | 98 | testRenderer.unmount(); 99 | t.end(); 100 | }); 101 | 102 | test('profunctor composition', t => { 103 | t.plan(8); 104 | const f = outer => outer.age; 105 | const g = age => age + 100; 106 | const h = age100 => age100 - 100; 107 | const i = age => ({age}); 108 | function Input({callbag}) { 109 | const level0 = useProfunctorState({age: 20}); 110 | const level1 = level0.promap(f, i); 111 | const level2 = level1.promap(g, h); 112 | React.useEffect(() => { 113 | callbag(0, (t, d) => { 114 | if (t === 1) level2.setState(() => d); 115 | }); 116 | () => { 117 | callbag(2); 118 | }; 119 | }, []); 120 | t.equal(g(f(level0.state)), level2.state, 'g . f compose'); 121 | return React.createElement('span', null, `My age is ${level2.state}`); 122 | } 123 | 124 | const makeCallbag = () => { 125 | let talkback; 126 | let value; 127 | return function callbag(t, d) { 128 | if (t === 0 && value) (talkback = d)(1, value); 129 | else if (t === 0) talkback = d; 130 | else if (t === 1 && talkback) talkback(1, (value = d)); 131 | else if (t === 1) value = d; 132 | else if (t === 2) (talkback = undefined), (value = undefined); 133 | }; 134 | }; 135 | 136 | const callbag = makeCallbag(); 137 | const elem = React.createElement(Input, {callbag}); 138 | const testRenderer = TestRenderer.create(elem); 139 | 140 | const result1 = testRenderer.toJSON(); 141 | t.ok(result1, 'should have rendered'); 142 | t.equal(result1.children.length, 1, 'should have one child'); 143 | t.equal(result1.children[0], 'My age is 120', 'should show 120'); 144 | 145 | callbag(1, 130); 146 | testRenderer.update(elem); 147 | 148 | const result2 = testRenderer.toJSON(); 149 | t.ok(result2, 'should have rendered'); 150 | t.equal(result2.children.length, 1, 'should have one child'); 151 | t.equal(result2.children[0], 'My age is 130', 'should show 130'); 152 | 153 | testRenderer.unmount(); 154 | t.end(); 155 | }); 156 | 157 | test('promap accepts lens object', t => { 158 | t.plan(8); 159 | const f = outer => outer.age; 160 | const g = age => age + 100; 161 | const h = age100 => age100 - 100; 162 | const i = age => ({age}); 163 | function Input({callbag}) { 164 | const level0 = useProfunctorState({age: 20}); 165 | const level1 = level0.promap({get: f, set: i}); 166 | const level2 = level1.promap({get: g, set: h}); 167 | React.useEffect(() => { 168 | callbag(0, (t, d) => { 169 | if (t === 1) level2.setState(() => d); 170 | }); 171 | () => { 172 | callbag(2); 173 | }; 174 | }, []); 175 | t.equal(g(f(level0.state)), level2.state, 'g . f compose'); 176 | return React.createElement('span', null, `My age is ${level2.state}`); 177 | } 178 | 179 | const makeCallbag = () => { 180 | let talkback; 181 | let value; 182 | return function callbag(t, d) { 183 | if (t === 0 && value) (talkback = d)(1, value); 184 | else if (t === 0) talkback = d; 185 | else if (t === 1 && talkback) talkback(1, (value = d)); 186 | else if (t === 1) value = d; 187 | else if (t === 2) (talkback = undefined), (value = undefined); 188 | }; 189 | }; 190 | 191 | const callbag = makeCallbag(); 192 | const elem = React.createElement(Input, {callbag}); 193 | const testRenderer = TestRenderer.create(elem); 194 | 195 | const result1 = testRenderer.toJSON(); 196 | t.ok(result1, 'should have rendered'); 197 | t.equal(result1.children.length, 1, 'should have one child'); 198 | t.equal(result1.children[0], 'My age is 120', 'should show 120'); 199 | 200 | callbag(1, 130); 201 | testRenderer.update(elem); 202 | 203 | const result2 = testRenderer.toJSON(); 204 | t.ok(result2, 'should have rendered'); 205 | t.equal(result2.children.length, 1, 'should have one child'); 206 | t.equal(result2.children[0], 'My age is 130', 'should show 130'); 207 | 208 | testRenderer.unmount(); 209 | t.end(); 210 | }); 211 | 212 | test('promap is spreadable', t => { 213 | t.plan(4); 214 | const f = outer => outer.age; 215 | const g = age => ({ age }); 216 | function Input() { 217 | const level0 = { ...useProfunctorState({ age: 20 }) }; 218 | t.ok(level0.promap, 'promap should be defined after spreading'); 219 | 220 | const level1 = level0.promap({ get: f, set: g }); 221 | return React.createElement('span', null, `My age is ${level1.state}`); 222 | } 223 | 224 | const elem = React.createElement(Input); 225 | const testRenderer = TestRenderer.create(elem); 226 | 227 | const result1 = testRenderer.toJSON(); 228 | t.ok(result1, 'should have rendered'); 229 | t.equal(result1.children.length, 1, 'should have one child'); 230 | t.equal(result1.children[0], 'My age is 20', 'should show 20'); 231 | 232 | testRenderer.unmount(); 233 | t.end(); 234 | }); 235 | 236 | test('promap is destructable', t => { 237 | t.plan(4); 238 | const f = outer => outer.age; 239 | const g = age => ({ age }); 240 | function Input() { 241 | const { promap } = useProfunctorState({ age: 20 }); 242 | t.ok(promap, 'promap should be defined after destructuring'); 243 | 244 | const level1 = promap({ get: f, set: g }); 245 | return React.createElement('span', null, `My age is ${level1.state}`); 246 | } 247 | 248 | const elem = React.createElement(Input); 249 | const testRenderer = TestRenderer.create(elem); 250 | 251 | const result1 = testRenderer.toJSON(); 252 | t.ok(result1, 'should have rendered'); 253 | t.equal(result1.children.length, 1, 'should have one child'); 254 | t.equal(result1.children[0], 'My age is 20', 'should show 20'); 255 | 256 | testRenderer.unmount(); 257 | t.end(); 258 | }); 259 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Profunctor State Hook 2 | 3 | *React Hook for state management with Profunctor Optics* 4 | 5 | A 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. 6 | 7 | ``` 8 | npm install --save @staltz/use-profunctor-state 9 | ``` 10 | 11 | See also [@staltz/**with**-profunctor-state](https://github.com/staltz/with-profunctor-state). 12 | 13 | ## Example 14 | 15 | Suppose 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)`. 16 | 17 | Open this also in a [CodeSandbox](https://codesandbox.io/s/3vz5vl5p5). 18 | 19 | ```js 20 | function App() { 21 | const initialState = {fahrenheit: 70, other: {}} 22 | 23 | const appProf = useProfunctorState(initialState); 24 | // or: 25 | // const {state, setState, promap} = useProfunctorState(initialState); 26 | 27 | const celsiusProf = appProf.promap( 28 | state => fToC(state.fahrenheit), 29 | (celsius, state) => ({ ...state, fahrenheit: cToF(celsius) }) 30 | ); 31 | 32 | return ( 33 |
34 |
Global app state: {JSON.stringify(appProf.state)}
35 | 36 |
37 | ); 38 | } 39 | ``` 40 | 41 | Because promap is composable, you can also split the conversion layer into multiple parts: 42 | 43 | ```js 44 | const celsiusProf = appProf 45 | .promap(s => s.fahrenheit, (f, s) => ({ ...s, fahrenheit: f })) 46 | .promap(fToC, cToF); 47 | ``` 48 | 49 | The CelsiusThermometer component received props `state`, `setState` and `promap` from the spread of `celsiusProf`: 50 | 51 | - `state`: in this case it's a number representing celsius 52 | - `setState`: does what you think it does! 53 | - `promap`: use this if CelsiusThermometer would have children components 54 | 55 | ```js 56 | function CelsiusThermometer({ state, setState, promap }) { 57 | const onColder = () => setState(prev => prev - 5); 58 | const onHotter = () => setState(prev => prev + 5); 59 | return ( 60 |
61 | 62 | 63 | 64 |
65 | ); 66 | } 67 | ``` 68 | 69 | ## Benefits 70 | 71 | #### Simpler architecture 72 | 73 | - Global app state == Props == Local component state 74 | - No actions, no reducers, no dispatch, no store 75 | - Selector/unselector conversion layers in the component tree 76 | 77 | #### Familiar 78 | 79 | - `state` and `setState` work just like you would assume 80 | - Easy to migrate your apps to use profunctors 81 | 82 | #### Fractal 83 | 84 | - Build the parent components like you build the smaller components 85 | - Same pattern applies to all components, both Presentational and Container 86 | 87 | #### Decoupling 88 | 89 | - Every child component assumes nothing about its parent component 90 | - Child components with props `{state, setState, promap}` can be published as-is to NPM 91 | 92 | #### Functional 93 | 94 | - Lenses are composable and operate immutably, just like Redux selectors 95 | - Chain `.promap` calls like you would chain `.map` calls 96 | - Backed by [mathematical theory](https://github.com/hablapps/DontFearTheProfunctorOptics/) 97 | 98 | #### Performance similar to Redux 99 | 100 | - Sprinkle `React.memo()` here and there to avoid full-app rerenders 101 | 102 | #### Small: 2 KB and 80 lines of code 103 | 104 | #### TypeScript support 105 | 106 | ## Downsides 107 | 108 | Compared to Redux and similar (ngrx, Vuex): 109 | 110 | - No actions means no support for Redux DevTools 111 | - This library itself is not used in production yet 112 | 113 | ## API 114 | 115 | #### `useProfunctorState(initial, [args])` 116 | 117 | ```js 118 | const {state, setState, promap} = useProfunctorState(initial); 119 | ``` 120 | 121 | React hook that should be called in the body of a function component. Returns a profunctor state object, which consists of three parts: 122 | 123 | - `state`: the data, initially this will be `initial` 124 | - `setState`: works just like React's traditional setState 125 | - `setState(newState)` or 126 | - `setState(prev => ...)` 127 | - `promap(get, set)`: creates a new profunctor state object based on the current one, given two functions: 128 | - `get: parentState => childState` 129 | - `set: (newChild, oldParent) => newParent` 130 | 131 | Promap 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. 132 | 133 | Underneath, 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]`. 134 | 135 | #### `withProfunctorState(ProComponent, initial, [args])` 136 | 137 | Higher-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. 138 | 139 | ## Pro Components 140 | 141 | A 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: 142 | 143 | ```js 144 | function CelsiusThermometer({ state, setState, promap }) { 145 | const onColder = () => setState(prev => prev - 5); 146 | const onHotter = () => setState(prev => prev + 5); 147 | return ( 148 |
149 | 150 | 151 | 152 |
153 | ); 154 | } 155 | ``` 156 | 157 | A 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. 158 | 159 | PS: it might be good to apply `React.memo()` on every Pro Component by default. 160 | 161 | ## FAQ 162 | 163 | #### What about performance? 164 | 165 | By 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). 166 | 167 | Check [this CodeSandbox](https://codesandbox.io/s/l5w1rpnv17) with `React.memo` usage, where background colors change upon re-render. 168 | 169 | #### Can I still have truly internal local state? 170 | 171 | Yes. Nothing stops you from adding a `useState` hook so you can have truly internal state in a Pro Component, such as: 172 | 173 | ```diff 174 | function CelsiusThermometer({ state, setState, promap }) { 175 | const onColder = () => setState(prev => prev - 5); 176 | const onHotter = () => setState(prev => prev + 5); 177 | + const [steps, setSteps] = useState(4); 178 | return ( 179 |
180 | 181 | 182 | - 183 | + 184 |
185 | ); 186 | } 187 | ``` 188 | 189 | #### Is this production-ready? 190 | 191 | Theoretically, 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. 192 | 193 | In 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. 194 | 195 | #### Why `@staltz/use-profunctor-state` and not `use-profunctor-state`? 196 | 197 | First, 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. 198 | 199 | ## License 200 | 201 | MIT 202 | --------------------------------------------------------------------------------