An open API service indexing awesome lists of open source software.

https://github.com/orir/react-composition-provider

HOC for creating components using the Composition Provider pattern
https://github.com/orir/react-composition-provider

component react react-context

Last synced: 10 months ago
JSON representation

HOC for creating components using the Composition Provider pattern

Awesome Lists containing this project

README

          

# react-composition-provider

`react-composition-provider` is a small react library that helps you implement a Composition Provider pattern.

The Composition Provider pattern tries to make prop drilling more ergonomic.

## Install

```
npm i --save react-composition-provider
```

## Usage

Let's look at a simple example of `Menu`, `Card`, `CardList`, `NewsList` & `WeatherList` components.
Where `CardList` contains `Card` and `Card` contains `Menu`.
Where `NewsList` & `WeatherList` both contain `CardList`.

We want to pass props from `NewsList` & `WeatherList` to `Menu`, this is how you do it:

```js
// Menu.js
import { withCompositionProvider } from 'react-composition-provider';

const InnerMenu = ({ cardId, items }) => {
// This is where the actual menu code would be.
};

export default withCompositionProvider(InnerMenu);

// Card.js
import Menu from './Menu';

export default (props) => {
return (




)
}
```

And in `NewsList` & `WeatherList` you'd do this:

```js
// NewsList.js
import Menu from 'Menu';
export default (props) => {
const actions = [{ text: 'remove' }, { text: 'star' }];
return (



)
}

// WeatherList.js
import Menu from 'Menu';
export default (props) => {
const actions = [{ text: 'remove location' }];
return (



)
}
```

And that's it!
Now every time you want to supply extra props to a decendant `Menu` component just render `Menu.Provider` with what you need!

This is the basic usage but the library gives you a lot more features than just this, like:

1. You can have multiple `Menu.Provider` in the same tree, that way even if someone is using `Menu.Provider` in a component you can't change you'll still be able to pass more props to it!
2. If you want to create a scope and make sure that no ancestor of type `Menu.Provider` can add props you can use `Menu.Provider.Scoped`.
3. It handles refs correctly, you wouldn't want to write ` true,
// An array of props to ignore from all the Providers up the chain.
ignore: [],
// The most advanced configuration.
// Gives you full control over the composition of each and every prop.
// Each key in this object is the prop name and the value is a function that composes it and returns said composition.
// Luckily, you don't have to think about it too much because react-composition-provider comes with a built-in set of composers.
// To use these composers just import it like this:
// import { composers } from 'react-composition-providers';
//
// There are 2 available variations for some of the composers, reverse or take.
// reverse - composes the same way but just in reverse order.
// take - an object with first, last & index that takes a single element from the array.
// Note that all of the composers implement the take capabiliy, the ones marked with reverse can also use that capability.
//
// These are the available composers:
// 1. composers.node() - combines an array of nodes as siblings using React.Fragment. (reverse)
// 2. composers.string(separator: String) - combines an array of strings with the given separator (defaults to " " - space). (reverse)
// 3. composers.object(merge: (objects: Array) => Object) - combines an array of objects by merging them together.
// the first one is the least specific and the last is the most specific.
// the merge callback takes in an array of objects and returns a merged object (defaults to Object.assign). (reverse)
// 4. composers.array() - combines an array of arrays to a single array (essentially a flatMap). (reverse)
// 5. composers.func.result.compose(composer: Composer) - executes each function in the array sequentially, the composer parameter is used to combine the results.
// 6. composers.func.result.ignore() - executes each function in the array sequentially and ignores the result.
// 7. composers.bool.and() - combines an array of booleans to a single boolean value by "&&" all of them together.
// 8. composers.bool.or() - combines an array of booleans to a single boolean value by "||" all of them together.
// 9. composers.number.sum() - gets the sum of the array of numbers.
// 10. composers.number.min() - gets the minimum number of the array of numbers.
// 11. composers.number.max() - gets the maximum number of the array of numbers
// 12. composers.number.average() - gets the average of the array of numbers.
// 13. composers.date.min() - gets the minimum date of the array of dates.
// 14. composers.date.max() - gets the maximum date of the array of dates.
compose: {},
});
```

As you can see this API is very flexible, but tries to have sane defaults.

## Why?
Prop drilling makes our components too specific, and tie the API of a component with the components it's composing (its implementation).
When these components change it will probably mean that the API will change too.
This applies to all the levels you "drill" the props, if something way down the line changes - all of them change.
What happens when you forget to drill some props? that's a tedious job of going through all the levels between where you have the prop value all the way down to the where it actually needs to be.

You might say that you can solve all of that with a state management library like `redux` - that's true.
You'd then need to define reducers, actions, and connect components to a store - just to pass some props down the line. It's like using a hammer for screws.

That's why `react-composition-provider` exists.
It creates a "portal" between the component itself and all the places you defined props for it.
It's very small (6kb non-gzipped) so you won't feel it, plus it'll probably help you save some boilerplate code so you code end up with a smaller bundle!.

## Use cases

Although this pattern can be very helpful and you're maybe thinking to yourself "I'm going to define all of my components like this now!".

**JUST DON'T**

General rules of thumb (there are probably more since this is new) of dos and don'ts with this pattern:

1. you're in control of both ends, the component itself and the provider(s).
1. you have to pass several props down the component tree.
2. you have a very deep component tree and don't want to cause re-rendering for all the components in the middle.
2. as a library author, exposing _some_ internal component API - this may make the library API less predictable.
1. when you define props like `xxxProps` - you already want to expose the props for `xxx` as part of your API.
2. it's important to also define a scope for your components so things don't leak outside your API boundary.

So, as with all patterns out there - make sure you understand what you're doing and don't abuse it.

## License

MIT