npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-composition-provider

v0.0.1

Published

A High Order Component library for components to follow the Composition Provider pattern

Downloads

9

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:

// 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 (
        <div>
            <Menu cardId={props.id}/>
        </dib>
    )
}

And in NewsList & WeatherList you'd do this:

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

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

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 <Menu.Provider ref={...} and get the ref of the provider, right?
  4. It can handle composition elegantly (already does so OOTB for, ref, style, className and children) and gives you a way to define the composition for each prop.
  5. When you want to re-define the children of the inner component you'd have to use a prop named kids because obviously the children prop is already taken.
  6. Allows you to conditionally mount the component. Even if the Menu was rendered somewhere, it doesn't mean that InnerMenu has to be mounted.
  7. Gives you a way to control which props can be propagated from a Menu.Provider and which aren't.

Most of these can be configured through a second options argument to withCompositionProvider:

import { withCompositionProvider, composers } from 'react-composition-provider';

export default withCompositionProvider(MyComponent, {
  // Object defining all the polyfills used by this package.
  polyfills: {
    // If you're using an old version of react that doesn't have React.createContext,
    // you'll need to supply a polyfill for it, something like - https://github.com/jamiebuilds/create-react-context
    createContext: React.createContext
  }
  // Allows you to control when the inner component actually mounts.
  // Returns true if the component should mount and false when it shouldn't.
  // props - all the composed props for this component
  // propsChain - an array of props, ordered by closeness to the actual component.
  //              where propsChain[0] is given directly to the component and propsChain[1] is the closest Provider, and so on.
  mountWhen: ({ props, propsChain }) => 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>) => 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