Skip to content

Commit

Permalink
0.4.0 bye bye SUB
Browse files Browse the repository at this point in the history
- removed SUB function, same can be achieved using functions now
- updated tests and docs to reflect removal of SUB
- don't recursively interpret return from top level function patch
- pass merge fn as second arg to scope function for convenience
  • Loading branch information
fuzetsu committed Aug 8, 2019
1 parent be1cc29 commit 529ed87
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 30 deletions.
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Mergerino works very well with the [meiosis](http://meiosis.js.org/) state manag
## ESM installation

```js
import merge, { SUB } from 'https://unpkg.com/mergerino?module'
import merge from 'https://unpkg.com/mergerino?module'

const state = {
user: {
Expand All @@ -29,7 +29,7 @@ const newState = merge(state, {
weight: undefined,
age: age => age / 2
},
other: SUB({ replaced: true })
other: () => ({ replaced: true })
})

/*
Expand Down Expand Up @@ -77,7 +77,9 @@ result = {
return age / 2
}
},
other: mergerino.SUB({ replaced: true })
other: function() {
return { replaced: true }
}
})
/*
Expand All @@ -99,7 +101,7 @@ result = {

## Usage Guide

Mergerino is made up of a single function `merge(target, ...patches)`, plus 1 helper function, `SUB`.
Mergerino is made up of a single function `merge(target, ...patches)`.

Patches in mergerino are expressed as plain JavaScript objects:

Expand Down Expand Up @@ -131,16 +133,16 @@ console.log(newState) // {}

Use `null` instead of `undefined` if you don't want the key to be deleted.

If you want to replace a property based on its current value, use a function. You can bypass merging logic and fully replace a property by using `SUB`.
If you want to replace a property based on its current value, use a function.

```js
const state = { age: 10, obj: { foo: 'bar' } }
const newState = merge(state, { age: x => x * 2, obj: SUB({ replaced: true }) })
const newState = merge(state, { age: x => x * 2, obj: () => ({ replaced: true }) })
console.log(state) // { age: 10, obj: { foo: 'bar' } }
console.log(newState) // { age: 20, obj: { replaced: true } }
```

If you pass a function it will receive the current value as an argument and the return value will be the replacement. If you use `SUB` the value you pass will bypass merging logic and simple overwrite the property (this is mainly useful for bypassing merging logic for objects or replacement logic for functions).
If you pass a function it will receive the current value as the first argument and the merge function as the second. The return value will be the replacement. The value you return will bypass merging logic and simply overwrite the property. This is useful when you want to replace an object without merging. If you would like to merge from within a function patch then use the merge function provided as the second argument.

## Multiple Patches

Expand Down Expand Up @@ -195,7 +197,7 @@ merge(state, {

Mergerino can be used as a reducer where patches are fed into a function which is then applied to a central state object. In these cases you may not have a reference to the full state object to base your patch on.

In order to help in this scenario mergerino supports passing a function as a top level patch. This function receives the merge target as an argument and treats the return value as a patch.
In order to help in this scenario mergerino supports passing a function as a top level patch. This function acts exactly the same as a function passed to a specific property. It receives the full state object as the first argument, the merge function as the second.

```js
// state-manager.js
Expand All @@ -205,10 +207,14 @@ const update = patch => (state = merge(state, patch))
// other.js
update({ newProp: true })
// want to use value of count to patch
update(state => ({ double: state.count * 2 }))
update((state, m) => m(state, { double: state.count * 2 }))

// back in state-manager.js
console.log(state) // { count: 10, newProp: true, double: 20 }

// if you don't use the merge function the top level object will be replaced
update(state => ({}))
console.log(state) // {}
```

## Credits
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mergerino",
"version": "0.3.0",
"version": "0.4.0",
"description": "immutable state merge util",
"source": "src/index.js",
"main": "dist/mergerino.es5.min.js",
Expand Down
9 changes: 2 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
const s = {}
export const SUB = r => ({ s, r })

const assign = Object.assign || ((a, b) => (b && Object.keys(b).forEach(k => (a[k] = b[k])), a))

const run = (isArr, copy, patch) => {
const type = typeof patch
if (patch && type === 'object') {
if (Array.isArray(patch)) for (const p of patch) copy = run(isArr, copy, p)
else if (patch.s === s) copy = patch.r
else {
for (const k of Object.keys(patch)) {
const val = patch[k]
if (typeof val === 'function') copy[k] = val(copy[k])
if (typeof val === 'function') copy[k] = val(copy[k], merge)
else if (val === undefined) isArr && !isNaN(k) ? copy.splice(k, 1) : delete copy[k]
else if (val === null || typeof val !== 'object' || Array.isArray(val)) copy[k] = val
else if (val.s === s) copy[k] = val.r
else if (typeof copy[k] === 'object') copy[k] = val === copy[k] ? val : merge(copy[k], val)
else copy[k] = run(false, {}, val)
}
}
} else if (type === 'function') copy = run(isArr, copy, patch(copy))
} else if (type === 'function') copy = patch(copy, merge)
return copy
}

Expand Down
21 changes: 9 additions & 12 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ o.spec('mergerino', () => {
const state = { age: 10, name: 'bob', obj: { prop: true } }
const newState = merge(state, {
age: x => x * 10,
obj: merge.SUB({ replaced: true })
obj: () => ({ replaced: true }),
name: (x, m) => {
o(m).equals(merge) // verify that merge is passed as second arg
return x
}
})
o(newState).deepEquals({ age: 100, name: 'bob', obj: { replaced: true } })
o(state).deepEquals({ age: 10, name: 'bob', obj: { prop: true } })
Expand Down Expand Up @@ -60,10 +64,10 @@ o.spec('mergerino', () => {
})
o('function patch', () => {
const state = { age: 10, foo: 'bar' }
const newState = merge(state, s => {
const newState = merge(state, (s, m) => {
o(s).notEquals(state)
o(s).deepEquals(state)
return { prop: true }
return merge(s, { prop: true })
})
o(newState).deepEquals({ age: 10, foo: 'bar', prop: true })
})
Expand All @@ -78,7 +82,7 @@ o.spec('mergerino', () => {
'',
0,
null,
() => ({ age: 10 }),
(s, m) => m(s, { age: 10 }),
[[[[[[[{ age: x => x * 3 }]]]]]]]
)
o(newState).notEquals(state)
Expand Down Expand Up @@ -110,14 +114,7 @@ o.spec('mergerino', () => {
o('top level SUB', () => {
const state = { age: 20, foo: 'bar' }
const replacement = { replaced: true }
const newState = merge(state, merge.SUB(replacement))
o(newState).notEquals(state)
o(newState).equals(replacement)
})
o('top level SUB from function patch', () => {
const state = { age: 20, foo: 'bar' }
const replacement = { replaced: true }
const newState = merge(state, () => merge.SUB(replacement))
const newState = merge(state, () => replacement)
o(newState).notEquals(state)
o(newState).equals(replacement)
})
Expand Down

0 comments on commit 529ed87

Please sign in to comment.