diff --git a/README.md b/README.md index 5c77640b..84910e89 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ The `realue` module exposes the following functions: > ⬇️ `{ value? }` -Sets `value` to `defaultValue` if `value` is `null`. +Sets `value` to `defaultValue` if `value` is `nil`. #### `transformable` @@ -196,6 +196,8 @@ Uses `title` as console group (defaults to decorated component name). Removes provided `propNames`. +### Decorator constructors + #### `onPropsChange(shouldHandleOrKeys, handler, callOnMount = true)` Similar to `withPropsOnChange`, except that the values of the `handler` are not merged into the props. @@ -239,6 +241,24 @@ The return value of the optional parent prop `[onPullName](newValue, previousVal Injects prop `[onCycleName](payload)` that cycles the value of prop `[name]` through the values found in prop `[valuesName]` which default to `[false, true]`. Calls `[onChangeName](value, name, payload)` with `name` taken from prop `[nameName]` or `name`. +#### `withChildren(Component, childProps?, shouldUpdateOrKeys?, valueName?)` + +> ⬆️ `{ [valueName]? }` + +> ⬇️ `{ children }` + +Builds an array that maps every item from the `[valueName]` prop with the result of ` ⬆️ `{ [valueName]? }` + +> ⬇️ `{ children }` + +Builds an element from the provided `Component` with the props from `childProps(props)` and injects it as a `children` prop. +The prop is only updated if `shouldUpdateOrKeys` returns `true` or if a prop whose name is listed in it changes. + ### Type-oriented decorators #### `object` diff --git a/demo/index.js b/demo/index.js index 1b188720..9c39f1f5 100644 --- a/demo/index.js +++ b/demo/index.js @@ -31,6 +31,8 @@ import { toggledEditing, transformable, syncedFocus, + withChildren, + withChild, } from '../src' const Text = compose( @@ -105,11 +107,12 @@ const Item = compose( const Items = compose( pure, array, -)(function Items({ value, item, onAddItem }) { + withChildren(Item), +)(function Items({ value, children, onAddItem }) { return $( 'div', null, - $('ul', null, map(value, (value, index) => $(Item, item(index)))), + $('ul', null, children), onAddItem && $(ItemCreator, { onChange: onAddItem, name: value.length }), ) }) @@ -176,7 +179,8 @@ const EditedItems = compose( onToggleEditing() }, }), -)(function EditedItems({ value, onChange, editing, onToggleEditing }) { + withChild(Items), +)(function EditedItems({ children, editing, onToggleEditing }) { return $( 'div', null, @@ -185,7 +189,7 @@ const EditedItems = compose( onChange: onToggleEditing, label: 'Edit', }), - $(Items, { value, onChange }), + children, ) }) diff --git a/src/tools.js b/src/tools.js index 1a50f457..b09e58bb 100644 --- a/src/tools.js +++ b/src/tools.js @@ -5,7 +5,6 @@ import { every, get, indexOf, - isFunction, isString, keys, memoize, @@ -13,6 +12,8 @@ import { slice, uniq, upperFirst, + map, + identity, } from 'lodash' import { branch, @@ -199,9 +200,10 @@ export function onPropsChange(shouldHandleOrKeys, handler, callOnMount = true) { Similar to `withPropsOnChange`, except that the values of the `handler` are not merged into the props. The `handler` is called when the component is first mounted if `callOnMount` is `true` (default value). */ - const shouldHandle = isFunction(shouldHandleOrKeys) - ? shouldHandleOrKeys - : (props, nextProps) => !same(props, nextProps, shouldHandleOrKeys) + const shouldHandle = + typeof shouldHandleOrKeys === 'function' + ? shouldHandleOrKeys + : (props, nextProps) => !same(props, nextProps, shouldHandleOrKeys) return lifecycle({ componentWillMount() { if (callOnMount) { @@ -361,6 +363,52 @@ export function cycledProp(options) { }) } +const DEFAULT_KEYS = ['value', 'name', 'onChange'] +const DEFAULT_CHILDREN_PROPS = ({ item }) => (value, index) => item(index) + +export function withChildren( + Component, + childProps = DEFAULT_CHILDREN_PROPS, + shouldUpdateOrKeys = DEFAULT_KEYS, + valueName = 'value', +) { + /* + Builds an array that maps every item from the `[valueName]` prop with the result of ` value => ({ value }))('ul') + */ + return withPropsOnChange(shouldUpdateOrKeys, props => ({ + children: map( + props[valueName], + (childProps => (value, index) => + $(Component, { + key: index, + ...childProps(value, index), + }))(childProps(props)), + ), + })) +} + +export function withChild( + Component, + childProps = identity, + shouldUpdateOrKeys = DEFAULT_KEYS, +) { + /* + Builds a child from the provided `Component` with the props from `childProps(props)` and injects it as a `children` prop. + The prop is only updated if `shouldUpdateOrKeys` returns `true` or if a prop whose name is listed in it changes. + */ + return withPropsOnChange(shouldUpdateOrKeys, props => ({ + children: $(Component, childProps(props)), + })) +} + export function lazyProperty(object, propertyName, valueBuilder) { /* Returns `object[propertyName]` if not `nil`, otherwise sets the result of `valueBuilder(object)` to it and returns it.