Configuring the Hierarchy
By default, the reactor hierarchy created by Zedux will return a JavaScript object for every branch node found in the hierarchy descriptor passed to the store. So:
store.use({ // this object is a b node
a: () => 1
})
creates a reactor hierarchy that'll return the following state:
{
a: 1
}
But what if we don't want an object? Say we want to enforce immutability in our store and use an immutable Map
as the intermediate nodes. We could build our own reactor hierarchy from scratch, but then we might as well be using plain Redux. Here's where store.setNodeOptions()
comes in.
The gist
There are 7 functions that Zedux uses to work with state tree nodes:
clone
- A function that accepts a node and returns a clone (shallow copy) of that node.create
- A function that returns a new, empty node.get
- A function that accepts a node and key and returns the value ofkey
innode
.isNode
- A function that accepts anything and returns a boolean indicating whether the given argument is a node.iterate
- A function that accepts a node and a callback function and iterates over all the key-value pairs, passing them to the callback function ascallback(key, value)
.set
- A function that accepts a node, a key, and a value and sets the value ofkey
tovalue
innode
.size
- A function that accepts a node and returns its size – an integer representing the number of key-value pairs contained in the node.
The default value of all these is pretty straight-forward:
const defaultNodeOptions = {
clone: node => { ...node },
create: () => ({}),
get: (node, key) => node[key],
isNode: isPlainObject, // (an internal helper function)
iterate: (node, callback) => {
// Iterate over the object's entries and pass each pair off
// to the callback function
Object.entries(node).forEach(
([ key, val ]) => callback(key, val)
)
},
set: (node, key, val) => {
node[key] = val
return node
},
size: node => Object.keys(node).length
}
This is the stuff that tells Zedux how to work with plain objects as the intermediate nodes in the reactor hierarchy. But using store.setNodeOptions()
, we can teach Zedux how to use something else.
Implementing
Let's teach Zedux a new data type. We'll use the Map
class from ImmutableJS for this example:
import { createStore } from 'zedux'
import { Map } from 'immutable'
const nodeOptions = {
clone: node => node,
create: () => new Map(),
get: (node, key) => node.get(key),
isNode: node => node instanceof Map,
iterate: (node, callback) => {
node.forEach(
(val, key) => callback(key, val)
)
},
set: (node, key, val) => node.set(key, val),
size: node => node.size
}
// A simple reducer for this example
const counterReducer = (state = 0, action) => {
const amount = action.type === increment.type
return state + amount
}
// And create the store
const store = createStore()
.setNodeOptions(options)
.use({
counter: counterReducer
})
// Now the root state tree node created by Zedux will be an Immutable map:
store.getState().get('counter') // 0
Notes
There are probably no reasons for overwriting just one or two options. If you specify one node option, you should definitely specify them all.