diff --git a/doc/computeAction.json b/docs/computeAction.json similarity index 100% rename from doc/computeAction.json rename to docs/computeAction.json diff --git a/dev.md b/docs/dev.md similarity index 100% rename from dev.md rename to docs/dev.md diff --git a/docs/motivation.md b/docs/motivation.md new file mode 100644 index 0000000..3cb63dd --- /dev/null +++ b/docs/motivation.md @@ -0,0 +1,49 @@ +## 📚 motivation + +- React is no more a `View` lib, it's now (v17) a complete framework: so either we pick a lighter lib for the `View`, or choosing React ❌ **we shouldn't need to use an additional external framework** such as Redux, MobX, RxJs, Recoil, Zustand, Jotail... + +- A first approach could be to use local states and sporadic use of React context, like [explained here by Kent C. Dodds](https://kentcdodds.com/blog/application-state-management-with-react), but ❌ it's not a proper Flux implementation, I'd rather have **my entire app state fully separated from the `View`**, and "connect" [containers](https://medium.com/@learnreact/container-components-c0e67432e005), mapping sub-states to the views, the way Redux allows to. + +- Using React context to propagate the global app state, like [suggested here by Rico Sta. Cruz](https://ricostacruz.com/til/state-management-with-react-hooks), [or here by Ebenezer Don](https://blog.logrocket.com/use-hooks-and-context-not-react-and-redux/), would be ok for a _small application_, but would quickly lead to ❌ **tons of useless re-renderings**. + +- That would eventually lead to lots of specific `useMemo` on every component requiring performance optimisation. + So rather than to put the effort on developping on a proper state/component architecture, your effort will be spent on ❌ **writing those `useMemo` everywhere**. + +- Eventually, all these steps lead me to `RxJs` which allows the use of **many stores**, by subscribing to their updates on `useEffect` and applying changes with `useState` I would have this **local rerendering** I want. + ❌ Well, that would mean adding a third party lib, and I'd like not to. + +- Finally, I've come accross [zustand](https://github.com/pmndrs/zustand), which resolves all these previous points and use React hooks only ! + That is the lib **the closest** to the way I want to manage my state, but ❌ I want to keep my redux-like reducers, actions and container connect function to map my stores locally to the required props for the finest re-rendering, and no extra-recipes (back to point 1) + +## 🧙 experimentation + +The idea with `Hookstores` is + +- ✅ to stay within React **only**, +- ✅ to implement a simple [Flux architecture](https://facebook.github.io/flux/docs/in-depth-overview) +- ✅ **splitting** the global app state into **stores** states, +- ✅ applying **local rendering**, by mapping these stores states to [containers](https://medium.com/@learnreact/container-components-c0e67432e005), using React hooks `useState` and `useEffect`. +- ✅ using React hooks to create the stores on app startup, then access `{dispatch, ...stores} = useStores()` everywhere: + + - `dispatch` allowing to emit actions to every store, and they now if they have to compute this action to reduce a new state. + + - your `stores` are accessible, with the `key` you give in `descriptions`, to allow kind of "mapStateToProps" on the connected containers, only when there _is_ an update: Now that's local re-rendering. + +## ☢️ disclaimer + +So yes, somehow it ends up as another lib to manage your React state 🙃. + +But since it's only few files you should understand what's behind the hood, then use and tweak them to your convenience _within your own React app_ rather than use it out of the box. + +You'll see `Hookstores` is rather few lines to **patch** React. + +Furthermore, + +- ⚠️ it's not written in typescript 🙀 +- ⚠️ there are no tests 💥 + +That being said, + +- ✅ I'm confidently using this implementation between many apps, +- ✅ so I prefer to have this package, +- ✅ so why not sharing this experiment. diff --git a/readme.md b/readme.md index 806e40f..aa1418e 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ [![devDeps](https://david-dm.org/uralys/hookstores/dev-status.svg)](https://david-dm.org/uralys/hookstores?type=dev) [![deps](https://david-dm.org/uralys/hookstores/status.svg)](https://david-dm.org/uralys/hookstores) -`Hookstores` is an elementary [Flux](https://facebook.github.io/flux/docs/in-depth-overview) implementation using React hooks and context. +`Hookstores` is an elementary [Flux](https://facebook.github.io/flux/docs/in-depth-overview) implementation using React hooks. ![action->dispatcher->store->view](https://facebook.github.io/flux/img/overview/flux-simple-f8-diagram-1300w.png) @@ -12,50 +12,7 @@ ## 📚 motivation -- React is no more a `View` lib, it's now (v17) a complete framework: so either we pick a lighter lib for the `View`, or choosing React ❌ **we shouldn't need to use an additional external framework** such as Redux, MobX, RxJs, Recoil, Jotail... - -- A first approach could be to use local states and sporadic use of React context, like [explained here by Kent C. Dodds](https://kentcdodds.com/blog/application-state-management-with-react), but ❌ it's not a proper Flux implementation, I'd rather have **my entire app state fully separated from the `View`**, and "connect" [containers](https://medium.com/@learnreact/container-components-c0e67432e005), mapping sub-states to the views, the way Redux allows to. - -- Using React context to propagate the global app state, like [suggested here by Rico Sta. Cruz](https://ricostacruz.com/til/state-management-with-react-hooks), [or here by Ebenezer Don](https://blog.logrocket.com/use-hooks-and-context-not-react-and-redux/), would be ok for a _small application_, but would quickly lead to ❌ **tons of useless re-renderings**. - -- That would eventually lead to lots of specific `useMemo` on every component requiring performance optimisation. - So rather than to put the effort on developping on a proper state/component architecture, your effort will be spent on ❌ **writing those `useMemo` everywhere**. - -- Eventually, all these steps lead me to `RxJs` which allows the use of **many stores**, by subscribing to their updates on `useEffect` and applying changes with `useState` I would have this **local rerendering** I want. - ❌ Well, that would mean adding a third party lib, and I'd like not to. - -## 🧙 experimentation - -The idea with `Hookstores` is - -- ✅ to stay within React **only**, -- ✅ to implement a simple [Flux architecture](https://facebook.github.io/flux/docs/in-depth-overview) -- ✅ **splitting** the global app state into **stores** states, -- ✅ applying **local rendering**, by mapping these stores states to [containers](https://medium.com/@learnreact/container-components-c0e67432e005), using React hooks `useState` and `useEffect`. -- ✅ using React context only to provide `Hookstores` with `{dispatch, ...stores}` everywhere, - - - `dispatch` allowing to emit actions to every store, and they now if they have to compute this action to reduce a new state. - - - your `stores` are accessible, with the `key` you give in `descriptions`, to allow kind of "mapStateToProps" on the connected containers, only when there _is_ an update: Now that's local re-rendering. - -## ☢️ disclaimer - -So yes, somehow it ends up as another lib to manage your React state 🙃. - -But since it's only few files you should understand what's behind the hood, then use and tweak them to your convenience _within your own React app_ rather than use it out of the box. - -You'll see `Hookstores` is rather few lines to **patch** React. - -Furthermore, - -- ⚠️ it's not written in typescript 🙀 -- ⚠️ there are no tests 💥 - -That being said, - -- ✅ I'm confidently using this implementation between many apps, -- ✅ so I prefer to have this package, -- ✅ so why not sharing this experiment. +read this [doc]('docs/motivation.md') --- @@ -69,15 +26,12 @@ That being said, ## 🛠 setup -Under the hood, the `Hookstores` uses [React Context](https://reactjs.org/docs/context.html). +Here is the path to follow to setup Hookstores on your app: -As such, you can wrap your root component, such as `App`, with the provider called `Hookstores` for convenience, to integrate it with your React app. - -```jsx - - - -``` +- 1: write descriptions: your redux-like reducers +- 2: create stores on startup +- 3: bind store data to your container state +- 4: dispatch actions to trigger stores changes --- @@ -89,10 +43,12 @@ In the following, let's illustrate how to use `Hookstores` with: --- -## ✍️ descriptions +## 1 ✍️ descriptions `Hookstores` will create stores, register for actions, and emit updates using the descriptions you provide. +![computeAction](https://user-images.githubusercontent.com/910636/103582817-e2d13600-4ede-11eb-8fbf-f0eb2a7cd3e7.png) + Each store has its own description, which must export: - an `initialState`, @@ -114,9 +70,10 @@ export default itemsStoreDescription; - you should use one store for one feature (here the `Items`) - define within `computeAction` how a store must update its state for every `handledAction`. -![computeAction](https://user-images.githubusercontent.com/910636/103582817-e2d13600-4ede-11eb-8fbf-f0eb2a7cd3e7.png) +
+Example -Here is the example for our illustrating `itemsStore`: +Here is the example for our illustrating `itemsStore` ```js /* ./features/items/store-description.js */ @@ -150,11 +107,13 @@ export default itemsStoreDescription; export {FETCH_ITEMS}; ``` +
+ --- -## 🛠 back to setup +## 2 🏁 creating the stores on startup -Once all descriptions are ready, you give them names, and pass them as parameters to `` +Once all descriptions are ready, you give them names, and pass them as parameters to `createStores()` ```js /* ./index.js */ @@ -162,34 +121,34 @@ Once all descriptions are ready, you give them names, and pass them as parameter import itemsStore from './features/items/store-description.js'; import anyOtherStore from './features/whatever/store-description.js'; - - -; +createStores({ + itemsStore, + anyOtherStore +}); ``` --- -## storeState ➡️ props +## 3: storeState ➡️ props -Use `connectStore` to register your container to store changes. +- Retrieve the store to connect provided by `useStores`. +- Use `connectStore` to register your container to store changes. +- Then map the store state to your component props, using your container state. +
+Example -Then map the store state to your component props, using your container state. +Here is the example for our illustrating `itemsStore` ```js /* ./features/items/container.js */ import React, {useLayoutEffect, useState} from 'react'; import ItemsComponent from './component'; -import {connectStore} from 'hookstores'; +import {connectStore, useStores} from 'hookstores'; const ItemsContainer = props => { const [items, setItems] = useState(); - const {itemsStore} = props; + const {itemsStore} = useStores(); useLayoutEffect(() => { const onStoreUpdate = storeState => { @@ -205,20 +164,24 @@ const ItemsContainer = props => { }; ``` +##
+ +--- + 🔍 Don't forget to return the `disconnect` function at the end of your hook, unless you will have stores updates triggering unmounted containers updates. --- -## 📡 dispatching actions +## 4 📡 dispatching actions Use [`prop drilling`](https://kentcdodds.com/blog/prop-drilling) from your containers to your components: pass functions dispatching the actions ```js -import {useHookstores} from 'hookstores'; +import {useStores} from 'hookstores'; import {SELECT_ITEM} from 'path/to/actions'; const ItemsContainer = props => { - const {dispatch} = useHookstores(); + const {dispatch} = useStores(); const selectItem = id => () => { dispatch({