Skip to content

Commit

Permalink
Merge pull request #89 from richardcrng/remove-mutate
Browse files Browse the repository at this point in the history
Remove mutate
  • Loading branch information
richardcrng authored Jun 23, 2019
2 parents 2045053 + 10951c2 commit 40feecd
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 76 deletions.
21 changes: 21 additions & 0 deletions docs/intro/demo.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ describe("Redux-Leaves. Write once. Reduce anywhere.", () => {

const reducersDict = {
double: leafState => leafState * 2,
splice: {
argsToPayload: (first, second) => [first, second],
mutate: true,
reducer: (leafState, { payload }) => { leafState.splice(...payload) }
},
appendToEach: (leafState, action) => leafState.map(str => str.concat(action.payload)),
countKeys: (leafState, action, wholeState) => Object.keys(wholeState).length
}
Expand Down Expand Up @@ -117,6 +122,22 @@ describe("Redux-Leaves. Write once. Reduce anywhere.", () => {
})
expect(initialState.nested.arbitrarily.deep).toBe(0)
})

describe("AND we dispatch actions.list.create.splice(0, 1)", () => {
beforeEach(() => {
store.dispatch(actions.list.create.splice(0, 1))
})

test("THEN the store's state.list updates non-mutatively", () => {
expect(store.getState()).toEqual({
counter: 4,
list: ['second item'],
nested: { arbitrarily: { deep: 4 } },
newKey: true
})
expect(initialState.list).toEqual(['first'])
})
})
})
})
})
Expand Down
91 changes: 57 additions & 34 deletions docs/leafReducers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@ They are:

### Function (shorthand)
```js
const leafReducer = (leafState, action, treeState) => {
const shorthandFunction = (leafState, action, treeState) => {
// some logic here
// return the new leafState
}
```

### Configuration object (longhand)
The above leafReducer function is shorthand for the following configuration object:
The above leafReducer function is shorthand for a configuration object with presets:
```js
const leafReducer = {
reducer: (leafState, action, treeState) => {
// some logic here
// return the new leafState
}
const longhandConfig = {
reducer: shorthandFunction,

// below are the configuration keys and their default values

argsToPayload: firstArgOnly => firstArgOnly,
// by default, if the action creator is invoked with arguments,
// the first argument only becomes the action's payload property.
}
```

Expand Down Expand Up @@ -62,50 +65,70 @@ The new state value for the leaf.

**Default behaviour:** if a first argument is provided, it is supplied as the action's payload. All other arguments are discarded.

```js
// Demonstration of default behaviour:
const argsToPayload = (first, ...rest) => first
```

#### Arguments
- `...args`: the arguments supplied to an action creator that triggers [`reducer`](#reducer)

#### Returns
A `payload` used by the action creator.

### `actionType`
*(string | function, optional)*: A string constant, or a function that returns a string, that becomes the action's `type` property
#### Examples
```js
// Action payload is the first argument only (default behaviour)
const firstArgToPayload = firstArgOnly => firstArgOnly

**Default behaviour:** if a first argument is provided, it is supplied as the action's payload. All other arguments are discarded.
// Action payload as an array of the first 5 arguments
const firstFiveArgsToPayload = (...args) => args.slice(0, 5)

// Action payload as an object
const spreadArgsToObjectPayload = (first, second, ...rest) => ({ first, second, rest })
```

We can check that these are behaving as expected:
```js
// Demonstration of default behaviour:
const actionType = (leaf, payload) => {
const {
path, // e.g. ['path', 'to', 'nested', 'state']
CREATOR_KEY // e.g. 'CUSTOM_CREATOR'
} = leaf
return [...path, CREATOR_KEY].join('/') // 'path/to/nested/state/CUSTOM_CREATOR'
}
// Test them out by creating actions using reduxLeaves
const returnPayload = (leafState, { payload }) => payload
[
firstArgToPayload,
firstFiveArgsToPayload,
spreadArgsToObjectPayload
].forEach(argsToPayload => {
// Use each as an argsToPayload
const returnPayload = {
reducer: (leafState, { payload }) => payload,
argsToPayload
}
const [reducer, actions] = reduxLeaves({}, { returnPayload })
// log out the payload for an action passed seven arguments
console.log(actions.create.returnPayload(1, 2, 3, 4, 5, 6, 7).payload)
})

// 1
// [1, 2, 3, 4, 5]
// { first: 1, second: 2, rest: [3, 4, 5, 6, 7] }
```

### `actionType`
*(string | function, optional)*: A string constant, or a function that returns a string, that becomes the action's `type` property

**Default behaviour:** if a first argument is provided, it is supplied as the action's payload. All other arguments are discarded.

#### Arguments
- `leaf`: the [`leaf` property](leaf/standardActions.md#properties) of the [Leaf Standard Action](leaf/standardActions.md) being created
- `payload`: the `payload` property of the Leaf Standard Action being created

#### Returns
A `type` property for the created action.

## Example
#### Examples
```js
const leafReducer = {
argsToPayload: (...args) => {
// some logic here
// return an action payload
},
reducer: (leafState, action, treeState) => {
// some logic here
// return the new leafState
}
}
let argsToPayload

// Default behaviour: action payload is the first argument only
argsToPayload = firstArgOnly => firstArgOnly

// Payload as an array of the first 5 arguments
argsToPayload = (...args) => args.slice(0, 5)

// Payload as an object
argsToPayload = (first, second, ...rest) => ({ first, second, rest })
```
117 changes: 117 additions & 0 deletions docs/leafReducers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,121 @@ describe("Function shorthand", () => {
})
})
})
})

describe("Object longhand", () => {
describe("argsToPayload", () => {
describe("GIVEN no argsToPayload defined in identity config object", () => {
const identity = {
reducer: leafState => leafState
}

describe("WHEN we initialise reduxLeaves with empty state and identity in the dictionary", () => {
const [reducer, actions] = reduxLeaves({}, { identity })
let store
beforeEach(() => {
store = createStore(reducer)
})

test("THEN the store's state is {}", () => {
expect(store.getState()).toEqual({})
})

describe("AND we create action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)", () => {
const action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)

test("THEN action.payload is 1", () => {
expect(action.payload).toBe(1)
})
})
})
})

describe("GIVEN argsToPayload = (..args) => args.slice(0, 5) in the identity config object", () => {
const identity = {
reducer: leafState => leafState,
argsToPayload: (...args) => args.slice(0, 5)
}

describe("WHEN we initialise reduxLeaves with empty state and identity in the dictionary", () => {
const [reducer, actions] = reduxLeaves({}, { identity })
let store
beforeEach(() => {
store = createStore(reducer)
})

test("THEN the store's state is {}", () => {
expect(store.getState()).toEqual({})
})

describe("AND we create action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)", () => {
const action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)

test("THEN action.payload is [1, 2, 3, 4, 5]", () => {
expect(action.payload).toEqual([1, 2, 3, 4, 5])
})
})
})
})

describe("GIVEN argsToPayload = (first, second, ...rest) => ({ first, second, rest }) in the identity config object", () => {
const identity = {
reducer: leafState => leafState,
argsToPayload: (first, second, ...rest) => ({ first, second, rest })
}

describe("WHEN we initialise reduxLeaves with empty state and identity in the dictionary", () => {
const [reducer, actions] = reduxLeaves({}, { identity })
let store
beforeEach(() => {
store = createStore(reducer)
})

test("THEN the store's state is {}", () => {
expect(store.getState()).toEqual({})
})

describe("AND we create action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)", () => {
const action = actions.create.identity(1, 2, 3, 4, 5, 6, 7)

test("THEN action.payload is { first: 1, second: 2, rest: [3, 4, 5, 6, 7] }", () => {
expect(action.payload).toEqual({ first: 1, second: 2, rest: [3, 4, 5, 6, 7] })
})
})
})
})
})

// describe("mutate", () => {
// describe("GIVEN the leaf reducer setPropTrue", () => {
// const setPropTrue = {
// reducer: (leafState, { payload }) => {
// leafState[payload] = true
// },
// mutate: true
// }

// describe("WHEN we initialise reduxLeaves with empty state and setPropTrue in the dictionary", () => {
// const [reducer, actions] = reduxLeaves({}, { setPropTrue })
// let store
// beforeEach(() => {
// store = createStore(reducer)
// })

// test("THEN the store's state is {}", () => {
// expect(store.getState()).toEqual({})
// })

// describe("AND we dispatch actions.create.setPropTrue('foobar')", () => {
// beforeEach(() => {
// store.dispatch(actions.create.setPropTrue('foobar'))
// })

// test("THEN the store's state is { foobar: true }", () => {
// expect(store.getState()).toEqual({ foobar: true })
// })
// })
// })
// })
// })
})
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "redux-leaves",
"version": "0.3.3",
"description": "Write once, reduce anywhere",
"version": "0.3.4",
"description": "Write once. Reduce anywhere",
"main": "dist/index.js",
"license": "MIT",
"author": {
Expand All @@ -11,7 +11,7 @@
},
"dependencies": {
"change-case": "^3.1.0",
"immer": "^3.0.0",
"immer": "^3.1.2",
"ramda": "^0.26.1",
"ramda-adjunct": "^2.18.0",
"redux": "^4.0.1"
Expand Down
21 changes: 15 additions & 6 deletions src/leafReducer/custom/leafReducerCustom.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
export const leafReducerCustom = (reducersDict, leafState, { leaf = {}, payload }, wholeState) => {
const { creatorKey } = leaf;
import produce from 'immer'

if (Object.keys(reducersDict).includes(creatorKey)) {
const logic = reducersDict[creatorKey]
return logic.reducer(leafState, { payload }, wholeState)
export const leafReducerCustom = (reducersDict, leafState, action, wholeState) => {
const { leaf: { creatorKey } } = action;

return Object.keys(reducersDict).includes(creatorKey)
? applyReducer(reducersDict[creatorKey], leafState, action, wholeState)
: leafState
}

const applyReducer = (config, leafState, action, wholeState) => {
if (config.mutate) {
return produce(leafState, draftLeafState => {
config.reducer(draftLeafState, action, wholeState)
})
} else {
return leafState
return config.reducer(leafState, action, wholeState)
}
}
Loading

0 comments on commit 40feecd

Please sign in to comment.