Skip to content

Commit

Permalink
updated readme with React.context removal
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdugne committed Feb 7, 2021
1 parent 5986a67 commit 079bcb8
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 74 deletions.
File renamed without changes.
File renamed without changes.
49 changes: 49 additions & 0 deletions docs/motivation.md
Original file line number Diff line number Diff line change
@@ -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.
111 changes: 37 additions & 74 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,15 @@
[![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)

---

## 📚 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')

---

Expand All @@ -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
<Hookstores>
<App />
</Hookstores>
```
- 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

---

Expand All @@ -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`,
Expand All @@ -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)
<details>
<summary>Example</summary>

Here is the example for our illustrating `itemsStore`:
Here is the example for our illustrating `itemsStore`

```js
/* ./features/items/store-description.js */
Expand Down Expand Up @@ -150,46 +107,48 @@ export default itemsStoreDescription;
export {FETCH_ITEMS};
```

</details>

---

## 🛠 back to setup
## 2 🏁 creating the stores on startup

Once all descriptions are ready, you give them names, and pass them as parameters to `<Hookstores>`
Once all descriptions are ready, you give them names, and pass them as parameters to `createStores()`

```js
/* ./index.js */

import itemsStore from './features/items/store-description.js';
import anyOtherStore from './features/whatever/store-description.js';

<Hookstores
descriptions={{
quizzesStore,
anyOtherStore
}}
>
<App />
</Hookstores>;
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.
<details>
<summary>Example</summary>

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 => {
Expand All @@ -205,20 +164,24 @@ const ItemsContainer = props => {
};
```

## </details>

---

🔍 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({
Expand Down

0 comments on commit 079bcb8

Please sign in to comment.