Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 102 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Preconf is just 400 bytes and works well with [preact-context-provider](https://

## Usage

Install: `npm i preconf`

```js
import preconf from 'preconf';
import Provider from 'preact-context-provider';
Expand Down Expand Up @@ -44,6 +46,89 @@ render(
// <span>hello, Stan</span>
```

**Collisions**

In the event that a configuration field is present as a mergeable object in both `context.config` and the default configuration object provided to `preconf`, the resulting prop passed to the enhanced component will be a merged object, with the configuration value from `context` overriding the value from the default configuration for keys that cannot be merged.

Default behavior:

```js
const defaults = { name: { first: 'Bob', } };
let configure = preconf(null, defaults);

let FooWithDefaults = configure('name')(Foo)

render(
<Provider config={{ name: { last: 'Jones' } }}>
<FooWithDefaults />
</Provider>
)
// Foo receives prop `name` as merged object: { first: 'Bob', last: 'Jones'}
```

To prevent the default behavior and prevent deep-merging mergeable objects, `mergeProps` in the `options` parameter can be set to `false`.

_Note: precedence will be given to the object in `context` unless `yieldToContext` in `options` is set to `false`._

Override with value from `context.config`:

```js
const defaults = { name: { first: 'Bob', } };
// Additional options parameter passed with mergeProps set to false
let configure = preconf(null, defaults, { mergeProps: false });

let FooWithDefaults = configure('name')(Foo)

render(
<Provider config={{ name: { last: 'Jones' } }}>
<FooWithDefaults />
</Provider>
)
// Foo receives prop `name` as unmerged object: { last: 'Jones'}
```

To prevent the default behavior and allow values in the default mergeable object to take precedence over values from the mergeable object from `context.config`, `yieldToContext` in the `options` parameter can be set to `false`.

Override value from `context.config` with value from default:

```js
const defaults = { name: { first: 'Bob', } };
// Additional options parameter passed with yieldToContext set to false
let configure = preconf(null, defaults, { mergeProps: false, yieldToContext: false });

let FooWithDefaults = configure('name')(Foo)

render(
<Provider config={{ name: { last: 'Jones' } }}>
<FooWithDefaults />
</Provider>
)
// Foo receives prop `name` as unmerged object: { first: 'Bob'}
```

To prevent the default behavior and override only specific top-level keys, `mergeProps` in the `options` parameter can be set to an `Array` of key names, where only the keys present in the `mergeProps` `Array` will be merged.

_Note: Selective key-merging only applies to top-level keys; nested keys of the same name are merged._

_Note: Can be used in combination with `yieldToContext` to determine precedence in merging select top-level keys._

Override only specific top-level keys:

```js
const defaults = { name: { first: 'Bob', }, location: { city: 'Hamburg' } };
// Additional options parameter passed with mergeProps set to an Array of keys to be merged
let configure = preconf(null, defaults, { mergeProps: ['location'] });

let FooWithDefaults = configure('name, location')(Foo)

render(
<Provider config={{ name: { last: 'Jones' }, location: { country: 'Germany' } }}>
<FooWithDefaults />
</Provider>
)
// Foo receives prop `name` as unmerged object: { last: 'Jones'}, and prop `location` as merged object: { country: 'Germany', city: 'Hamburg' }
```

## API

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
Expand All @@ -54,8 +139,11 @@ Creates a higher order component that provides values from configuration as prop

**Parameters**

- `namespace` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** If provided, exposes `defaults` under a `namespace`
- `defaults` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** An object containing default configuration values
- `namespace` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** If provided, exposes `defaults` under a `namespace`
- `defaults` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** An object containing default configuration values
- `options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** An object containing options for resolving configuration values (optional, default `{}`)
- `options.mergeProps` **([Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) \| [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array))?** A boolean indicating whether props should be merged, or an array indicating which keys in props should be merged (optional, default `true`)
- `options.yieldToContext` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** A boolean where false indicates that the default values should override those from context, else values in context take precedence (optional, default `true`)

**Examples**

Expand All @@ -80,14 +168,23 @@ export default configure({
);
```

Returns **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** [configure()](#configure)
```javascript
// context.config = { location: { city: 'Hamburg' }}

let configure = preconf(null, { location: { country: 'Germany' } });
export default configure('location')( (props) =>
<span>Location: {`${props.location.city}, ${props.location.country}`}</span>
);
```

Returns **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** [configure()](#configure)

#### configure

Creates a Higher Order Component that provides configuration as props.

**Parameters**

- `keys` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>)** An object where the keys are prop names to pass down and values are dot-notated keypaths corresponding to values in configuration. If a string or array, prop names are inferred from configuration keys.
- `keys` **([Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>)** An object where the keys are prop names to pass down and values are dot-notated keypaths corresponding to values in configuration. If a string or array, prop names are inferred from configuration keys.

Returns **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** configureComponent(Component) -> Component
Returns **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** configureComponent(Component) -> Component
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"preact": "*"
},
"dependencies": {
"deepmerge": "^1.5.0",
"dlv": "^1.1.0"
}
}
6 changes: 4 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ export default {
useStrict: false,
external: [
'preact',
'dlv'
'dlv',
'deepmerge'
],
globals: {
preact: 'preact',
dlv: 'dlv'
dlv: 'dlv',
deepmerge: 'deepmerge'
},
plugins: [
buble({
Expand Down
33 changes: 28 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { h } from 'preact';
import delve from 'dlv';
import merge from 'deepmerge';

/** Creates a higher order component that provides values from configuration as props.
* @param {String} [namespace] If provided, exposes `defaults` under a `namespace`
* @param {Object} [defaults] An object containing default configuration values
* @param {String} [namespace] If provided, exposes `defaults` under a `namespace`
* @param {Object} [defaults] An object containing default configuration values
* @param {Object} [options] An object containing options for resolving configuration values
* @param {Boolean|Array} [options.mergeProps] A boolean indicating whether props should be merged, or an array indicating which keys in props should be merged
* @param {Boolean} [options.yieldToContext] A boolean where false indicates that the default values should override those from context, else values in context take precedence
* @returns {Function} [configure()](#configure)
*
* @example
Expand All @@ -23,8 +27,17 @@ import delve from 'dlv';
* })( ({ url }) =>
* <a href={props.url} />
* );
*
* @example
* // context.config = { location: { city: 'Hamburg' }}
*
* let configure = preconf(null, { location: { country: 'Germany' } });
* export default configure('location')( (props) =>
* <span>Location: {`${props.location.city}, ${props.location.country}`}</span>
* );
*/
export default function preconf(namespace, defaults) {
export default function preconf(namespace, defaults, { mergeProps=true, yieldToContext=true }={}) {

if (namespace) defaults = { [namespace]: defaults };

/** Creates a Higher Order Component that provides configuration as props.
Expand All @@ -43,8 +56,18 @@ export default function preconf(namespace, defaults) {
for (let key in keys) {
let path = keys[key];
if (isArray) key = path.split('.').pop();
if (typeof props[key]==='undefined' || props[key]===null) {
props[key] = delve(context, 'config.'+path, delve(defaults, path));

let inheritedVal = delve(context, 'config.'+path);
let defaultVal = delve(defaults, path);

let deepMerge = Array.isArray(mergeProps) ? ~mergeProps.indexOf(path) : mergeProps;

if (deepMerge && inheritedVal && defaultVal && typeof inheritedVal === 'object' && typeof defaultVal === 'object') {
props[key] = yieldToContext ? merge(defaultVal, inheritedVal) : merge(inheritedVal, defaultVal);
}

else if (typeof props[key]==='undefined' || props[key]===null) {
props[key] = yieldToContext ? inheritedVal || defaultVal : defaultVal || inheritedVal;
}
}
return h(Child, props);
Expand Down
Loading