Skip to content

Commit

Permalink
Merge pull request #118 from richardcrng/master-v0.6.1
Browse files Browse the repository at this point in the history
Master v0.6.1
  • Loading branch information
richardcrng authored Nov 7, 2019
2 parents d1f43a6 + 98f375d commit bd9d9b3
Show file tree
Hide file tree
Showing 81 changed files with 2,142 additions and 3,005 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
include:
- stage: Check compilation
node_js: node
script: npm build
script: npm run build

- stage: Produce Coverage
node_js: node
Expand Down
92 changes: 8 additions & 84 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
id: redux-leaves
title: Core API
title: reduxLeaves
hide_title: true
sidebar_label: reduxLeaves API
sidebar_label: reduxLeaves
---

# `reduxLeaves(initialState, [reducersDict = {}])`
Expand All @@ -13,7 +13,7 @@ Returns a reducer function and an object.

## Parameters
- [`initialState`](#initialstate) *(object)*: the state shape and initial values for your Redux store
- [`reducersDict`](#reducersdict) *(object, optional)*: a collection of [leaf reducers](leafReducers.md) keyed by their [creator keys](creatorKeys.md)
- [`reducersDict`](#reducersdict) *(object, optional)*: a collection of [leaf reducers](api/leafReducers.md) keyed by their [creator keys](api/creatorKeys.md)

### `initialState`
*(object)*
Expand All @@ -38,8 +38,8 @@ const initialState = {
*(object)*

This is an object where every `key`-`value` pair is such that:
- `value` *(function | object)* is a [leaf reducer](leafReducers.md);
- `key` is a [creator key](creatorKeys.md) for that leaf reducer.
- `value` *(function | object)* is a [leaf reducer](api/leafReducers.md);
- `key` is a [creator key](api/creatorKeys.md) for that leaf reducer.

#### Example

Expand All @@ -56,90 +56,14 @@ const reducersDict = {
## Returns
`array`, with two elements:
- 0th: `reducer` *(function)*: a reducer function to pass to redux's `createStore`
- 1st: `actions` *(object)*: an object with same shape as `initialState`
- 1st: [`actions`](api/actions.md) *(object)*: an object with same shape as `initialState`

### `reducer`

The root reducer for your Redux store.

It listens to actions created through [`actions`](#actions) at a given leaf for a given [creator key](creatorKeys.md), and updates that leaf's state using the [leaf reducer](leafReducers.md) keyed by the creator key.
It listens to actions created through [`actions`](api/actions.md) at a given leaf for a given [creator key](api/creatorKeys.md), and updates that leaf's state using the [leaf reducer](api/leafReducers.md) keyed by the creator key.

### `actions`

An object with nested action creator functions, following the same shape as the `initialState` passed to `reduxLeaves`.

#### Example

First, grab `reducer` and `actions` by destructuring the return value of `reduxLeaves`:

```js
import { createStore } from 'redux'
import reduxLeaves from 'reduxLeaves'

const initialState = {
counter: 1,
foo: ['bar']
nested: {
deep: {}
state: {
manageable: 'maybe...?'
}
}
}

const [reducer, actions] = reduxLeaves(initialState)
const store = createStore(reducer)
```

`actions` is an object with the same shape as `initialState`, with corresponding branches and leaves.

Every action branch and leaf is an object, regardless of the initial data type:

```js
console.log(typeof actions.counter) // 'object'
console.log(typeof actions.nested.deep) // 'object'
console.log(typeof actions.nested.state.manageable) // 'object'
```

Every action branch and leaf also has an additional property, `create`, that is an object:

```js
console.log(typeof actions.counter.create) // 'object'
console.log(typeof actions.nested.deep.create) // 'object'
console.log(typeof actions.nested.state.manageable.create) // 'object'
```

This `create` property is the API through which you can access action creators.

For example, suppose I want to dispatch an action that will increment my `counter` state by 2.

```js
// We can destructure to access the 'increment' method from the create API
const { increment } = actions.counter.create

// Increment is an action creator function which takes one argument
const action = increment(2)

// Dispatch the action to the store
store.dispatch(action)

// The store's state changes as expected!
console.log(store.getState())
/*
* {
* counter: 3, <--------------- incremented by 2!
* foo: ['bar'],
* nested: {
* deep: {},
* manageable: 'maybe...?'
* }
* }
*/
```
This API allows for concise but descriptive dispatching of actions.
```js
// Push 'FOO' to the 'foo' (array) slice of state
// by creating and dispatching an action

store.dispatch(actions.foo.create.push('FOO'))
```
See documentation on the [`actions`](api/actions.md) object.
67 changes: 67 additions & 0 deletions docs/api/actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
id: actions
title: actions
hide_title: true
sidebar_label: actions
---

# `actions`

## Arbitrary property paths

The `actions` object returned by `reduxLeaves` can take an arbitrary path of properties after it, which typically correspond to a 'leaf' at the corresponding path from your state.

```js
import reduxLeaves from 'redux-leaves'

const initialState = {
counter: 0,
arbitrary: {
nested: {
path: ['hi!']
}
}
}

const [reducer, actions] = reduxLeaves(initialState)

// state.counter
console.log(typeof actions.counter) // 'object'

// state.arbitrary.nested
console.log(typeof actions.arbitrary.nested) // 'object'

// state.arbitrary.nested.path
console.log(typeof actions.arbitrary.nested.path) // 'object'

// but also works for paths not in your initial state
console.log(typeof actions.not.in.my.initial.state) // 'object'
```

## Accessing `create`

For a given arbitrary property path, you have two options: navigating to a deeper property / leaf, or accessing the [`create`](create.md) property.

```js
// Access create at the state root
console.log(typeof actions.create) // 'function'

// Go deeper
console.log(typeof actions.arbitrary) // 'object'

// Access create at state.arbitrary
console.log(typeof actions.arbitrary.create) // 'function'

// Go deeper
console.log(typeof actions.arbitrary.nested.path)

// Access create at state.arbitary.nested.path
console.log(typeof actions.arbitrary.nested.path.create) // 'function'
```

Once you've accessed `create`, you can't go arbitrarily deeper beyond that.
```js
console.log(typeof actions.create.arbitrary) // 'undefined'
```

This is because the `create` key accesses the Redux-Leaves [action creator (`create`) API](create.md) at the corresponding leaf of state.
33 changes: 33 additions & 0 deletions docs/api/actions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import reduxLeaves from '../../src';

describe('actions can take an arbitrary path of properties after it', () => {
const initialState = {
counter: 0,
arbitrary: {
nested: {
path: ['hi!']
}
}
}

const [reducer, actions] = reduxLeaves(initialState)

test('Any arbitrary path returns an object', () => {
expect(typeof actions.counter).toBe('object')
expect(typeof actions.arbitrary.nested).toBe('object')
expect(typeof actions.arbitrary.nested.path).toBe('object')
expect(typeof actions.not.in.my.initial.state).toBe('object')
})

test('Any arbitrary path has the create function property', () => {
expect(typeof actions.create).toBe('function')
expect(typeof actions.counter.create).toBe('function')
expect(typeof actions.arbitrary.nested.create).toBe('function')
expect(typeof actions.arbitrary.nested.path.create).toBe('function')
expect(typeof actions.not.in.my.initial.state.create).toBe('function')
})

test("You can't access arbitrary paths from create", () => {
expect(typeof actions.create.arbitrary).toBe('undefined')
})
})
100 changes: 100 additions & 0 deletions docs/api/create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
id: create
title: create
hide_title: true
sidebar_label: create
---

# `create`

When you access the `create` property from any arbitrary path from the [`actions`](actions.md) object, you access the Redux-Leaves action creators API.

Consider the `actions` object returned below.
```js
import { createStore } from 'redux'
import reduxLeaves from 'redux-leaves'

const initialState = {
counter: 0,
arbitrary: {
nested: {
path: ['hi!']
}
}
}

const reducersDict = {
convertToFoobar: () => 'foobar'
}

const [reducer, actions] = reduxLeaves(initialState, reducersDict)
```

* `actions.counter.create` corresponds to creating actions at `state.counter`;
* `actions.arbitrary.nested.path.create` corresponds to creating actions at `state.arbitrary.nested.path`;
* `actions.create` corresponds to creating actions at the `state`'s root.

## `create[creatorKey]`
### Action creators
You can access action creators (functions) from the `create` API at any leaf by using their [`creatorKey`](creatorKeys.md) as a property, both for the default ones and any custom ones you've defined.

```js
// Example defaults: update, set, push
console.log(typeof actions.counter.create.update) // 'function'
console.log(typeof actions.arbitrary.nested.create.set) // 'function'
console.log(typeof actions.arbitrary.nested.path.create.push) // 'function'

// Custom creatorKey of 'convertToFoobar'
console.log(typeof actions.create.convertToFoobar) // 'function'
console.log(typeof actions.arbitrary.nested.path.create.convertToFoobar) // 'function'
```

### Actions
Executing these functions then create the actions that you should dispatch to your Redux store.

```js
const store = createStore(reducer) // using reducer from reduxLeaves
console.log(store.getState().counter) // 0

const updateCounter = actions.counter.create.update

store.dispatch(updateCounter(5))
console.log(store.getState().counter) // 5

store.dispatch(updateCounter(3))
console.log(store.getState().counter) // 3
```

### Optional `actionType` argument
Rather than directly accessing action creators from `create`, you can optionally provide an `actionType` string as an argument to `create` before accessing the action creator functions:

```js
// Defaults, e.g. update creatorKey
console.log(typeof actions.counter.create.update) // 'function'
console.log(typeof actions.counter.create('UPDATE_COUNTER').update) // 'function'

// Custom creatorKey of 'convertToFoobar'
console.log(typeof actions.create.convertToFoobar) // 'function'
console.log(typeof actions.create('CONVERT_TO_FOOBAR').convertToFoobar) // 'function'
```

Providing an `actionType` string in this way does not change the way that the reducer will respond to actions; it merely overrides the created action's `type` property to be the string passed in (which might be desirable for a debugging perspective for Redux DevTools, for example):

```js
const createDefaultIncrement = actions.counter.create.increment
const createNamedIncrement = actions.counter.create('NAMED_INCREMENT').increment

console.log(store.getState().counter) // 3

const defaultIncrement = createDefaultIncrement()
console.log(defaultIncrement.type) // 'counter/INCREMENT'
dispatch(defaultIncrement)
console.log(store.getState().counter) // 4

const namedIncrement = createNamedIncrement()
console.log(namedIncrement.type) // 'NAMED_INCREMENT'
dispatch(namedIncrement)
console.log(store.getState().counter) // 5
```

(It is safe to override name in this way because the Redux-Leaves `reducer` does not switch over the action's `type`.)
Loading

0 comments on commit bd9d9b3

Please sign in to comment.