Giter Club home page Giter Club logo

react-composer's Introduction

React Composer

Travis build status npm version npm downloads Test Coverage gzip size

Compose render prop components.

Motivation

Render props are great. Using a component with a render prop looks like the following:

<RenderPropComponent {...config}>
  {result => <MyComponent result={result} />}
</RenderPropComponent>

Sometimes you need the result of multiple render prop components inside of MyComponent. This can get messy.

<RenderPropComponent {...config}>
  {resultOne => (
    <RenderPropComponent {...configTwo}>
      {resultTwo => (
        <RenderPropComponent {...configThree}>
          {resultThree => (
            <MyComponent results={{ resultOne, resultTwo, resultThree }} />
          )}
        </RenderPropComponent>
      )}
    </RenderPropComponent>
  )}
</RenderPropComponent>

Nesting render prop components leads to rightward drift of your code. Use React Composer to prevent that drift.

import Composer from 'react-composer';

<Composer
  components={[
    <RenderPropComponent {...configOne} />,
    <RenderPropComponent {...configTwo} />,
    <RenderPropComponent {...configThree} />
  ]}>
  {([resultOne, resultTwo, resultThree]) => (
    <MyComponent results={{ resultOne, resultTwo, resultThree }} />
  )}
</Composer>;

Installation

Install using npm:

npm install react-composer

or yarn:

yarn add react-composer

API

This library has one, default export: Composer.

<Composer />

Compose multiple render prop components together. The props are as follows:

props.children

A render function that is called with an array of results accumulated from the render prop components.

<Composer components={[]}>
  {results => {
    /* Do something with results... Return a valid React element. */
  }}
</Composer>

props.components

The render prop components to compose. This is an array of React elements and/or render functions that are invoked with a render function and the currently accumulated results.

<Composer
  components={[
    // React elements may be passed for basic use cases
    // props.children will be provided via React.cloneElement
    <Outer />,

    // Render functions may be passed for added flexibility and control
    ({ results, render }) => (
      <Middle previousResults={results} children={render} />
    )
  ]}>
  {([outerResult, middleResult]) => {
    /* Do something with results... Return a valid React element. */
  }}
</Composer>

Note: You do not need to provide props.children to the React element entries in props.components. If you do provide props.children to these elements, it will be ignored and overwritten.

props.components as render functions

A render function may be passed instead of a React element for added flexibility.

Render functions provided must return a valid React element. Render functions will be invoked with an object containing 2 properties:

  1. results: The currently accumulated results. You can use this for render prop components which depend on the results of other render prop components.
  2. render: The render function for the component to invoke with the value produced. Plug this into your render prop component. This will typically be plugged in as props.children or props.render.
<Composer
  components={[
    // props.components may contain both elements and render functions
    <Outer />,
    ({ /* results, */ render }) => <SomeComponent children={render} />
  ]}>
  {results => {
    /* Do something with results... */
  }}
</Composer>

Examples and Guides

Example: Render prop component(s) depending on the result of other render prop component(s)

<Composer
  components={[
    <Outer />,
    ({ results: [outerResult], render }) => (
      <Middle fromOuter={outerResult} children={render} />
    ),
    ({ results, render }) => (
      <Inner fromOuterAndMiddle={results} children={render} />
    )
    // ...
  ]}>
  {([outerResult, middleResult, innerResult]) => {
    /* Do something with results... */
  }}
</Composer>

Example: Render props named other than props.children.

By default, <Composer /> will enhance your React elements with props.children.

Render prop components typically use props.children or props.render as their render prop. Some even accept both. For cases when your render prop component's render prop is not props.children you can plug render in directly yourself. Example:

<Composer
  components={[
    // Support varying named render props
    <RenderAsChildren />,
    ({ render }) => <RenderAsChildren children={render} />,
    ({ render }) => <RenderAsRender render={render} />,
    ({ render }) => <CustomRenderPropName renderItem={render} />
    // ...
  ]}>
  {results => {
    /* Do something with results... */
  }}
</Composer>

Example: Render prop component(s) that produce multiple arguments

Example of how to handle cases when a component passes multiple arguments to its render prop rather than a single argument.

<Composer
  components={[
    <Outer />,
    // Differing render prop signature (multi-arg producers)
    ({ render }) => (
      <ProducesMultipleArgs>
        {(one, two) => render([one, two])}
      </ProducesMultipleArgs>
    ),
    <Inner />
  ]}>
  {([outerResult, [one, two], innerResult]) => {
    /* Do something with results... */
  }}
</Composer>

Limitations

This library only works for render prop components that have a single render prop. So, for instance, this library will not work if your component has an API like the following:

<RenderPropComponent onSuccess={onSuccess} onError={onError} />

Render Order

The first item in the components array will be the outermost component that is rendered. So, for instance, if you pass

<Composer components={[<A/>, <B/>, <C/>]}>

then your tree will render like so:

- A
  - B
    - C

Console Warnings

Render prop components often specify with PropTypes that the render prop is required. When using these components with React Composer, you may get a warning in the console.

One way to eliminate the warnings is to define the render prop as an empty function knowning that Composer will overwrite it with the real render function.

<Composer
  components={[
    <RenderPropComponent {...props} children={() => null} />
  ]}
  // ...
>

Alternatively, you can leverage the flexibility of the props.components as functions API and plug the render function in directly yourself.

<Composer
  components={[
    ({render}) => <RenderPropComponent {...props} children={render} />
  ]}
  // ...
>

Example Usage

Here are some examples of render prop components that benefit from React Composer:

Do you know of a component that you think benefits from React Composer? Open a Pull Request and add it to the list!

Contributing

Are you interested in helping out with this project? That's awesome – thank you! Head on over to the contributing guide to get started.

react-composer's People

Contributors

erikthedeveloper avatar jakxz avatar jamesplease avatar thexpand avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-composer's Issues

Make sure that the bundle is optimized

.5kb isn't "big" in absolute terms, but it seems big for what this component does.

My prediction is that some part of the prop-types lib is sticking around in the bundle.

Exposal of `chainComponents`

Could chainComponents be exported separately? Components with render props are really tedious to chain but there's no need to mount a Component so I can chain them.

Also, wouldn't it be chainElements? Just because when you say Component, you are talking about the descriptor where the element is the description. Once this function will act only with elements, saying components is wrong by definition.

Inconsistency in README and reversed order

This is from the README as of today:

image

I may be missing something, but it looks to me thaty the example without using react-composer shows the components in the same order as they are then listed in the example that uses react-composer.

But late on in the README it is stated that the order in which you list components in the components={[ ... ]} prop is the reverse of how they'll be merged. It says that the last you list will be the outer one. But the screenshot above does not seem to reflect that.

Update `components` prop name to be more accurate?

The components prop is a little misleading, as @erikthedeveloper points out. You might think you can place components in there, but you can't. It only accepts elements (or functions that return elements).

Because of this, one could argue that a more accurate name for this prop would be elements.

The downside to this wording is that I think many people gloss over the difference between components and elements (that's exactly what I did, which is why the prop is named components in the first place). This runs the risk of making things more confusing in exchange for the accuracy.

What do folks think? components=[<A/>] or elements=[<A/>]?

Leave a πŸ‘ or a πŸ‘Ž to this issue to share your opinion!

And feel free to comment, too, of course :)

Support composing components with differing render prop names

Currently, we only support composing components which all have the same render prop name via props.renderPropName (defaults to children). This eliminates the possibility of composing together components with differing render prop names such as children, render, someCustomRenderPropName.

One potential, non-breaking, API I've come up with (open to suggestions for alternatives here πŸ˜„ ) is introducing a props.renderPropNames which would be an array of strings, being the same length as components, so that components[n]'s render prop would map to renderPropNames[n].

I came up with this as a non-breaking solution trying to remain consistent with renderPropName.

renderPropNames would take precedence over renderPropName if both somehow were present.

To better describe this, let me dump this test here that I was playing with locally. I was able to get this working fairly easily:

  describe('Configurable render prop names', () => {
    test('It supports composing components with differing render prop names', () => {
      const renderPropComponent = renderPropName => props =>
        props[renderPropName](props.value);

      const renderPropNames = ['children', 'render', 'customName'];
      const [AsChildren, AsRender, AsCustomName] = renderPropNames.map(
        renderPropComponent
      );

      const wrapper = mount(
        <Composer
          components={[
            <AsChildren value="one" />,
            <AsRender value="two" />,
            <AsCustomName value="three" />
          ]}
          renderPropNames={renderPropNames}
          children={results => <MyComponent results={results} />}
        />
      );

      expect(wrapper.find(MyComponent).prop('results')).toEqual([
        'one',
        'two',
        'three'
      ]);
    });
  });

Some other potential APIs that come to mind:

<Composer
  components={[
    {component: <AsCustomName value="three" />, renderPropName: 'customName'},
    <SimpleCaseStillWorks />,
    // ...
  ]}
  children={/* ... */}
/>

<Composer
  components={[
    <AsCustomName value="three" renderPropName="customName" />,
    // ...
  ]}
  children={/* ... */}
/>

Usage with Apollo Graphql Query

Using Composer with Apollo Graphql Query render prop throws a warning that this.props.children is a required prop for a Query render prop and I guess Composer is not passing in any. Although this does not break any functionality but It'd be nice to remove that error.

Add `mapResult`

mapResult would be a function that receives the same arguments as the render prop, and lets you map it to a single value. So you could, for instance, do:

mapResult: function () { return Array.from(arguments); }

to add support for render prop components that don't pass a single arg.

Poll: breaking API change?

To cast your vote, please leave a reaction emoji of πŸ‘ or πŸ‘Ž to this issue!


@erikthedeveloper proposed a really cool API for Composer over in #39 . The change is this: instead of components callbacks receiving results, they receive ({ results, render }). You can then manually specify the render function as the render prop of the component.

Here is @erikthedeveloper 's example from that PR, showing it in use:

<Composer
  components={[
    // React elements may be passed for simple/basic use cases.
    // Assumes single value produced such as `props.children(producedValue)`
    <Echo value="outer" />,

    // OR A function may be passed to produce an element.

    // Utilizing outer results for inner components
    ({ render, results: [outerResult] }) => (
      <Echo value={`${outerResult.value} + middle`} children={render} />
    ),
    
    // Differing render prop signature (multi-arg producers)
    ({ /* results, */ render }) => (
      <DoubleEcho value="spaghetti">
        {(one, two) => render([one, two])}
      </DoubleEcho>
    ),
    
    // Differing render prop names...
    ({ /* results, */ render }) => (
      <EchoRenderProp value="spaghetti" renderProp={render} />
    )
    ]}
  ]}
  children={/* ... */}
/>

Negatives

  • This is a breaking API change.

Positives

  • Reduces API surface area. renderPropName and mapResult are no longer needed, and no functionality is lost
  • Provides a good solution to the issue where you would get console warnings for omitting the render prop ( #42 )
  • Supports more easily composing render prop components with different render prop names ( #37 )

tl;dr, it is a flexible, small, and powerful API that is a big breaking change from what we currently have. What do people think? Should it be changed?

Add your vote to the πŸ‘ / πŸ‘Ž tally below!

Basic Functional JSX Composition

I'm looking for a library to do simple functional composition of JSX elements. React-composer library looks like it might do what I need but I wonder if it has the simple use cases covered. This is not directly obvious to me from the documentation.

My current code has things that look like this.

<Foo x="123">
  <Bar y="456">
    <Quux />
  </Bar>
</Foo>

I'm wondering if the following react-composer notation would be equal to the above.

<Composer components={[
  <Foo x="123" />,
  <Bar y="456" />,
  <Quux />,
]} />

Support passing results of outer component(s) to inner component(s)

As I was investigating whether react-composer supported passing the results of outer render prop components to inner render prop components, I saw that this topic recently came up in #7 as @jmeas described as:

[...] a mode where each component gets passed the results of the past one. This allows for some very sophisticated composition of function components [...]

// From https://github.com/jmeas/react-composer/issues/7#issuecomment-362946205
<Composer
  // Opt into props.components as functions
  functional
  components={[
    (resultThree, resultTwo) => <RenderPropComponent {...configOne} />,
    (resultThree) => <RenderPropComponent {...configTwo} />,
    () => <RenderPropComponent {...configThree} />
  ]}>
  {([resultOne, resultTwo, resultThree]) => (
    <MyComponent results={[resultOne, resultTwo, resultThree]} />
  )}
</Composer>

This is something I commonly need when manually composing render prop components so I'm excited to see it being discussed here πŸ˜ƒ .

Some thoughts I have around the idea:

Keeping signature consistent

Where the primary Composer.props.children signature is

([resultOne, resultTwo]) => {/* ... */}

I feel like it would be more consistent to follow that for the Composer.props.components entries as functions also.

So rather than

[
  (resultOne, resultTwo) => {/* ... */},
  (resultOne) => {/* ... */},
  () => {/* ... */},
]

It would be

[
  ([resultOne, resultTwo]) => {/* ... */},
  ([resultOne]) => {/* ... */},
  ([]) => {/* ... */},
]

Support both element and function without requiring opt-in flag

Part of me likes the explicit props.functional from @jmeas example, but part of me wonders if both element and function could both be supported via a type check so this is a first class API rather than an opt-in alternative API.

Proposed API example usage (assuming a shift to first:Outer last:Inner as being discussed in #7)

<Composer
  components={[
    <Outer {...outerProps} />,
    <Middle {...middleProps} />,
    ([outerResult, middleResult]) => <InnerNeedsResults {...outerResult} {...middleResult} />,
  ]}>
  {([outerResult, middleResult, innerResult]) => (
    <MyComponent results={[outerResult, middleResult, innerResult]} />
  )}
</Composer>

As for how to achieve this, I roughly played with this idea after looking at this section of chainComponents

https://github.com/jmeas/react-composer/blob/02d11f844e40e09633fd2f069e1d96cd8339d410/src/index.js#L43-L52

and came up with this (totally untested ⚠️ πŸ˜„)

let component = components[componentIndex];

// ... 

if (typeof component === 'function') {
  // This entry from props.components is a function!
  // Invoke it with the currently accumulated results to produce the element.
  component = component(results);
}

// component is an element as it always was. Business as usual...
return React.cloneElement(component, {/* ... */});

πŸ“ As I was thinking about this and writing this, I realized just how confusing it can be with either RTL or LTR ordering. I might suggest using language such as "outer", "middle" , "inner" in the README and examples. That just feels more explicit/clear to me and eliminates a bit of mental parsing (especially for those not super familiar with composition in general and especially within React).

Perfomance: unnecessary rerendering

Hi. First of all thanks for your work!

But here's i investigate some perfomance issues. I'm currently using why-did-you-render lib and have some interesting warnings:

2018-12-21 13 41 16

(another 3 warns is the same)

I believe react-composer changing component's children more than 1 time so it's produces some unnecessary rendering. Here I have 4 rerenders due to props.children !== prevProps.children

Thanks.

Reverse the render order

Should the first component listed be the outermost?


Libs with right-to-left as the only behavior or the default behavior:

  • Redux
  • Apollo
  • React Request (presently)

Libs with left-to-right as the only behavior or the default behavior:

  • Lodash

TypeScript support (revisited)

Further to #40 it is possible to provide TypeScript support for the library (albeit in a slightly imperfect way), despite the somewhat tricky API to type, and current TS limitations.

A basic start to types is shown below - as you can see there needs to be a specific type for each number of components that is passed in the array, which is not ideal (and is the case solved by variadic types in TS as mentioned in the previous issue), but it can be made to work. It is the same approach taken by other libraries such as Ramda.

With respect to where to publish them, it is generally preferred to publish with the package itself where possible. This allows any API change to be immediately reflected in the types, and prevents installing the wrong version of types for the wrong version of library. Very little effort is needed to publish them - generally and index.d.ts file inside the package, with a ``types: "index.d.ts"` entry in the package.json.

If there is still appetite for supporting this then I'm happy to expand on the below and make a PR.

/// <reference types="react" />

declare module "react-composer" {
    interface ReactComposerProps1<T> {
        components: [
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: T) => React.ReactElement<any>;
                }>
            >
        ];
        children: (injected: [T]) => React.ReactElement<any>;
    }

    interface ReactComposerProps2<T, U> {
        components: [
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: T) => React.ReactElement<any>;
                }>
            >,
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: U) => React.ReactElement<any>;
                }>
            >
        ];
        children: (injected: [T, U]) => React.ReactElement<any>;
    }

    interface ReactComposerProps3<T, U, V> {
        components: [
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: T) => React.ReactElement<any>;
                }>
            >,
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: U) => React.ReactElement<any>;
                }>
            >,
            ReturnType<
                React.FunctionComponent<{
                    children?: (injected: V) => React.ReactElement<any>;
                }>
            >
        ];
        children: (injected: [T, U, V]) => React.ReactElement<any>;
    }

    class ReactComposer<T> extends React.Component<ReactComposerProps1<T>> {}
    class ReactComposer<T, U> extends React.Component<
        ReactComposerProps2<T, U>
    > {}
    class ReactComposer<T, U, V> extends React.Component<
        ReactComposerProps3<T, U, V>
    > {}

    export default ReactComposer;
}

Make `children` the default

React's new context API seems to be going in this direction. Once that is finalized, I will make the switch ✌️

Unit testing <Composer> with nested <Fetch> components?

Hi! Thanks for the great libraries! I'm running into some issues trying to write enzyme unit tests for a component that follows a pattern like this, where Fetch comes from react-request:

<Composer components=[FetchComponentA, FetchComponentB] />
  {([fetchA, fetchB]) => {
     if (fetchA.failed || fetchB.failed) {
         return <ErrorPage ... />;
     }
     if (fetchA.fetching || fetchB.fetching) {
         return <LoadingPage ... />;
     }
     return <HappyPage ... />;
  }}
</Composer>

Do you have any examples you can share of how to test this pattern? I seem to be having trouble getting at the underlying promise chain, so my tests complete before fetchA, fetchB, fetchC all resolve.

Thanks!

Clean up branches?

@erikthedeveloper , I noticed there is an old branch for a pretty cool update you had in mind for mapResult. With your other cool update that eventually ended up landing, that prop doesn’t exist anymore. Do you think we could/should delete the mapResult branch?

How to Compose Formik and Graphql Apollo Mutation

I want to access Mutation's render prop function in the Formik Submit handler

          <Composer
        components={[
          <Mutation
            mutation={SIGNUP}
            children={() => null}
          />,
          <Formik 
            initialValues={{ body: "", title: "", tags: "" }}
            onSubmit={()=>{
              //access SIGNUP function
            }}
          />
        ]}
      >
        {([])=> {

        }}
      </Composer>

How to access sign up function in the onSubmit

Add `functional` prop

functional would give developers more control over the components.

tl;dr, the result of each component is passed into the next. This would allow for async render prop components to work in series based off of the results of the previous component.


Update The name "serial" doesn't quite communicate how this works, because it doesn't enforce serial rendering, it just enables it.

Here's an example of what the API might look like:

<Composer functional components={[
    () => <RenderPropComponent {...configOne} />,
    (resultOne) => <RenderPropComponent {...configTwo} />,
    (resultOne, resultTwo) => <RenderPropComponent {...configThree} />
  ]}>
  {([resultOne, resultTwo, resultThree]) => (
    <MyComponent results={[resultOne, resultTwo, resultThree]} />
  )}
</Composer>

Named components

There could be an alternative syntax that supports named components. In this mode, order is not guaranteed, but you get an object back rather than an array.

It's much nicer to do results.someComponent rather than results[0].

API:

<Composer components={{
    one: <RenderPropComponent {...configOne} />,
    two: <RenderPropComponent {...configTwo} />,
    three: <RenderPropComponent {...configThree} />
  }}>
  {(results) => (
    <MyComponent results={results} />
  )}
</Composer>

What do folks think?

Leave a πŸ‘ or πŸ‘Ž on this comment to let me know!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.