/* Returns a function `(value, merge) => {...}` that calls `component.setState(...)` to update the component's state with the new value for `key`. A simple example: const func = updateKey(component, "counter"); func(23); which is equivalent to: component.setState({counter: 23}); `key` can be a nested path into an Object delimited by `.` For example: updateKey(comp, "a.b.c")(value) is equivalent to: comp.setState({ a: { ...comp.state.a, b: { ...comp.state.a.b, c: value } } }) Note that `a` and `b` are shallowly cloned to prevent direct mutations to `comp.state`. One main purpose of this function is to eliminate a lot of bookkeeping associated with nested object cloning. There is also a `merge` argument that allows the `value` to be merged into the Object located at `key`. For example: updateKey(comp, "a.b")(value, true) // explicitly merge updateKey(comp, "a.b", {merge: true})(value) // merge by default updateKey(comp, "a.b", {merge: false})(value, true) // merge; ignore default are all equivalent to: comp.setState({ a: { ...comp.state.a, b: { ...comp.state.a.b, ...value } } }) `opts` can be passed to `updateKey` to change some behavior: - `merge` - Sets the default `merge` value for the returned function - `cb` - Callback to be passed to `setState` and called after setting state. */ export function updateKey(component, key, opts) { opts = opts || {}; // Return a function that calls `setState` with custom updater and `cb` return (value, merge) => component.setState((state, props) => { // Split the `key` using "." as delimiter to determine path const path = key.split("."); // Create a `newState` object to be returned const newState = {}; // `state` will point to the current Object in `state` // `obj` will point to the current Object in `newState` let obj = newState; // `i` will keep track of how deep the Object `path` is let i; // Deep clone everything along the path to make React happy and // avoid mutating `this.state` directly for (i = 0; i < path.length - 1; i++) { const target = state[path[i]] instanceof Array ? [] : {}; obj[path[i]] = Object.assign(target, state[path[i]]); // Go deeper into `obj` and `state` state = state[path[i]] || {}; // Note: use {} as fail-safe obj = obj[path[i]]; } // Update the value on the deeply cloned Object // There are 2 options: merge Object state or replace state if (merge === undefined) merge = opts.merge; if (merge && typeof value === "object") { // Merge state const target = state[path[i]] instanceof Array ? [] : {}; obj[path[i]] = Object.assign(target, state[path[i]], value); } else { // Replace state obj[path[i]] = value; } return newState; }, opts.cb); } /* Returns a function `(e) => {...}` that calls `component.setState(...)` to update the component's state with the new value for `key` using `e.target.value` as the value. See `updateKey` above for more details. If `e.target` has a `name` property, `{[e.target.name]: e.target.value}` will be merged into `key` instead of replacing the value at `key` with `e.target.value`. Usage in React `render()` function: In the above code, when the is changed, the state's "counter" key will be updated to match the value of the . Another example: This is roughly equivalent to this monstrosity: this.setState({ a: { ...comp.state.a, b: { ...comp.state.a.b, c: { ...comp.state.a.b.c, d: e.target.value } } } }) } /> In the above code, when the is changed, `this.setState(...)` will be called such that `this.state.a.b.c` is merged with `{d: value}` where `value` matches the current value of the . `opts` can be passed to change some behavior: - `preventDefault` - By default, `e.preventDefault()` is called. To prevent this call, set `preventDefault` explicitly to `false` ... All other `opts` for `updateKey` */ export function updateKeyEvent(component, key, opts) { opts = opts || {}; const update = updateKey(component, key, opts); if (opts.preventDefault !== false) { // Call `preventDefault` by default unless explicitly set otherwise return e => { e.preventDefault(); const { name, value } = e.target; if (name) { // Special case: merge Object into `key` return update({ [name]: value }, true); } else { return update(value); } }; } else { return e => update(e.target.value); } }