Repository: ctrlplusb/easy-peasy
Pull request #38 (2018-12-06 11:00:32)

Adds a `meta` parameter to effects
This PR adds a `meta` parameter to effects. This parameter exposes both the parent path and the path of the effect being actioned.

```javascript
const store = createStore({
  foo: {
    doSomething: effect((dispatch, payload, getState, injections, meta) => {
      console.log(meta);
      // {
      //   parent: ['foo'],
      //   path: ['foo', 'doSomething']
      // }
    })
  }
});

await store.dispatch.foo.doSomething()
```

This should open mechanism by which to alleviate issues described within #36.

codecov[bot]: # [Codecov](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=h1) Report
> Merging [#38](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=desc) into [master](https://codecov.io/gh/ctrlplusb/easy-peasy/commit/06261a08c25d30907c3506ebab7df5c145d58343?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38/graphs/tree.svg?width=650&token=8XR3ly4qMt&height=150&src=pr)](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master    #38   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           5      5           
  Lines         167    167           
  Branches       29     29           
=====================================
  Hits          167    167
```


| [Impacted Files](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/easy-peasy.js](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38/diff?src=pr&el=tree#diff-c3JjL2Vhc3ktcGVhc3kuanM=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=footer). Last update [06261a0...39b7894](https://codecov.io/gh/ctrlplusb/easy-peasy/pull/38?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 60643c0..d9bf890 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-> Note: this package depends on the new [Hooks](https://reactjs.org/docs/hooks-intro.html) feature of React. Currently available via 16.7.0-alpha.0 of React.
+> Note: this package depends on the new [Hooks](https://reactjs.org/docs/hooks-intro.html) feature of React. Currently available via 16.7.0-alpha.2 of React.
 
 <p>&nbsp;</p>
 <p align='center'>
@@ -103,6 +103,8 @@ function TodoList() {
     - [StoreProvider](#storeprovider)
     - [useStore(mapState, externals)](#usestoremapstate-externals)
     - [useAction(mapAction)](#useactionmapaction)
+  - [Tips and Tricks](#tips-and-tricks)
+    - [Generalising effects/actions/state via helpers](#generalising-effectsactionsstate-via-helpers)
   - [Prior Art](#prior-art)
 
 <p>&nbsp;</p>
@@ -122,8 +124,8 @@ Easy Peasy gives you the power of Redux (and its tooling) whilst avoiding the bo
 Firsly, ensure you have the correct versions of React (i.e. a version that supports Hooks) installed.
 
 ```bash
-npm install react@16.7.0-alpha.0
-npm install react-dom@16.7.0-alpha.0
+npm install react@16.7.0-alpha.2
+npm install react-dom@16.7.0-alpha.2
 ```
 
 Then install Easy Peasy.
@@ -620,6 +622,36 @@ Declares an action on your model as being effectful. i.e. has asynchronous flow.
 
       Any depenencies that were provided to the `createStore` configuration will be exposed as this argument. See the [`createStore`](#createstoremodel-config) docs on how to specify them.
 
+    - `meta` (Object, required)
+
+      This object contains meta information related to the effect. Specifically it contains the following properties:
+
+        - parent (Array, string, required)
+
+          An array representing the path of the parent to the action.
+
+        - path (Array, string, required)
+
+          An array representing the path to the action.
+
+      This can be represented via the following example:
+
+      ```javascript
+      const store = createStore({
+        products: {
+          fetchById: effect((dispatch, payload, getState, injections, meta) => {
+            console.log(meta);
+            // {
+            //   parent: ['products'],
+            //   path: ['products', 'fetchById']
+            // }
+          })
+        }
+      });
+
+      await store.dispatch.products.fetchById()
+      ```
+
 When your model is processed by Easy Peasy to create your store all of your actions will be made available against the store's `dispatch`. They are mapped to the same path as they were defined in your model. You can then simply call the action functions providing any required payload.  See the example below.
 
 #### Example
@@ -1001,6 +1033,91 @@ const EditTodo = ({ todo }) => {
 
 ----
 
+## Tips and Tricks
+
+Below are a few useful tips and tricks when using Easy Peasy.
+
+### Generalising effects/actions/state via helpers
+
+You may identify repeated patterns within your store implementation. It is possible to generalise these via helpers.
+
+For example, say you had the following:
+
+```javascript
+const store = createStore({
+  products: {
+    data: {},
+    ids: select(state => Object.keys(state.data)),
+    fetched: (state, products) => {
+      products.forEach(product => {
+        state.data[product.id] = product;
+      });
+    },
+    fetch: effect((dispatch) => {
+      const data = await fetchProducts();
+      dispatch.products.fetched(data);
+    })
+  },
+  users: {
+    data: {},
+    ids: select(state => Object.keys(state.data)),
+    fetched: (state, users) => {
+      users.forEach(user => {
+        state.data[user.id] = user;
+      });
+    },
+    fetch: effect((dispatch) => {
+      const data = await fetchUsers();
+      dispatch.users.fetched(data);
+    })
+  }
+})
+```
+
+You will note a distinct pattern between the `products` and `users`. You could create a generic helper like so:
+
+```javascript
+import _ from 'lodash';
+
+const data = (endpoint) => ({
+  data: {},
+    ids: select(state => Object.keys(state.data)),
+    fetched: (state, items) => {
+      items.forEach(item => {
+        state.data[item.id] = item;
+      });
+    },
+    fetch: effect((dispatch, payload, getState, injections, meta) => {
+      //                                                     👆
+      // We can get insight into the path of the effect via the "meta" param
+      const data = await fetchProducts();
+      // Then we utilise lodash to map to the expected location for our
+      // "fetched" action
+      //                 👇
+      const fetched = _.get(dispatch, meta.parent.join('fetched'));
+      fetched(data);
+    })
+})
+```
+
+You can then refactor the previous example to utilise this helper like so:
+
+```javascript
+const store = createStore({
+  products: {
+    ...data(fetchProducts)
+    // attach other state/actions/etc as you like
+  },
+  users: {
+    ...data(fetchUsers)
+  }
+})
+```
+
+This produces an implementation that is like for like in terms of functionality but far less verbose.
+
+----
+
 ## Prior art
 
 This library was massively inspired by the following awesome projects. I tried to take the best bits I liked about them all and create this package. Huge love to all contributors involved in the below.
diff --git a/src/easy-peasy.js b/src/easy-peasy.js
index 432fd06..23a1070 100644
--- a/src/easy-peasy.js
+++ b/src/easy-peasy.js
@@ -60,7 +60,7 @@ export const createStore = (model, options = {}) => {
     initialState = {},
     injections,
     compose,
-    reducerEnhancer = reducer => reducer,
+    reducerEnhancer = rootReducer => rootReducer,
   } = options
 
   const definition = {
@@ -103,6 +103,10 @@ export const createStore = (model, options = {}) => {
               payload,
               references.getState,
               injections,
+              {
+                parent: parentPath,
+                path,
+              },
             )
           }
           action.actionName = actionName


Test patch:
diff --git a/src/__tests__/easy-peasy.test.js b/src/__tests__/easy-peasy.test.js
index 87be49f..de43d45 100644
--- a/src/__tests__/easy-peasy.test.js
+++ b/src/__tests__/easy-peasy.test.js
@@ -720,6 +720,27 @@ describe('effects', () => {
     // act
     await store.dispatch.doSomething()
   })
+
+  test('meta values are exposed', async () => {
+    // arrange
+    let actualMeta
+    const store = createStore({
+      foo: {
+        doSomething: effect((dispatch, payload, getState, injections, meta) => {
+          actualMeta = meta
+        }),
+      },
+    })
+
+    // act
+    await store.dispatch.foo.doSomething()
+
+    // assert
+    expect(actualMeta).toEqual({
+      parent: ['foo'],
+      path: ['foo', 'doSomething'],
+    })
+  })
 })
 
 describe('dev tools', () => {