Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mehrdad-shokri/1b070b74831fc7487847262d71a95cad to your computer and use it in GitHub Desktop.
Save mehrdad-shokri/1b070b74831fc7487847262d71a95cad to your computer and use it in GitHub Desktop.
Transforming Elements in React

Transforming Elements in React

Introduction

Transforming elements can be interesting if you're a library author, but can also be very valuable when writing components to abstract behaviour in your existing code base.

To get a better understanding, we will walkthrough the most important React Top Level API transformation functionalities. React offers a couple of helper functions that we can leverage when for creating and adapting elements, further information is available via the official React documentation.

Let's take a closer look at what we can take-away in the first section and then build a component that leverages the newly learnt features.

Basics

import React from "react";
import ReactDOM from "react-dom";

render is our main function, which we will use to render all our examples.

const render = Component =>
  ReactDOM.render(Component, document.getElementById("root"));

Let's build a Title element and a stateless component called App.

const Title = <h1>Transforming Elements</h1>;
const App = () => <div>{Title}</div>;

console.log("Title is valid?", React.isValidElement(Title)); // => true
console.log("App is valid?", React.isValidElement(App)); // => false

With the help of isValidElement we can check, as the name already implies, if the element is a React element or not. The following checks validate that Title is an actual React element, while App itself is not, as it's a function. When calling App we get a valid element back, which can be verified via the next isValidElement check.

console.log("<App /> is valid?", React.isValidElement(<App />)); // => true

What could be a useful case for needing these checks? For example if we need to know if we have to create or clone an element. Which brings us to the next interesting feature, createElement and respectively cloneElement.

Even if you're wondering what createElement is, you've actually already used this function, even if unknowingly. When we wrote

<h1>Transforming Elements</h1>

what actually happened was that this JSX definition was converted to:

React.createElement("h1", {}, "Transforming Elements")

Let's build the exact definition and render it to the screen.

const TitleH1 = React.createElement(
  "h1",
  null,
  "Transforming Elements"
);

render(TitleH1);

We can already verify that this is working as expected. Let's take a closer look at the createElement arguments.

React.createElement(type, [props], [...children])

As we can see from the above definition, we need a valid type, but what happens when we try to invoke it with an invalid element?

const TitleTest = React.createElement(
  "test",
  null,
  "Transforming Elements"
);

render(TitleTest);

A console warning is logged:

Warning: The tag <test> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

So React can tell when we're passing in an invalid node type. But the warning also reveils another interesting information:

If you meant to render a React component, start its name with an uppercase letter.

This message indicates that we can pass in a React component as the first argument. Let's try this out.

const TitleSubtitle = ({ children, title }) => (
  <div>
    <h1>{title}</h1>
    <h2>{children}</h2>
  </div>
);

const SubtitleComponent = React.createElement(
  TitleSubtitle,
  null,
  "Rendering more content"
);

render(TitleSubtitleComponent);

We can see that the children content is rendered into the H2 tag, but how can we define the title? If you recall, createElement accepts props optionally as a second argument. So our next step is to define the title we want displayed.

const TitleSubtitleComponent = React.createElement(
  TitleSubtitle,
  { title: "Transforming Elements" },
  "Rendering more content"
);

render(TitleSubtitleComponent);

We can verify that this works. Now that we have a basic understanding of what happens when we use JSX, let's take a look at another useful React top level API feature.

cloneElement has the following definition:

React.cloneElement(element, [props], [...children])

At first glance, this looks very similar to the previously explained createElement, with one important distinction, it expects a valid React element. So, let's go back to isValidElement again and verify that our current component actually works as expected.

console.log("TitleSubtitleComponent is valid?", React.isValidElement(TitleSubtitleComponent)); // => true

We already know that we have a valid element, otherwise we wouldn't have been able to previously render the component, but also gave us another opportunity to run isValidElement one more time.

Next let's assume we have a third value we want to display inside our component.

const TitleSubtitleInfo = ({ children, title, info }) => (
  <div>
    <h1>{title}</h1>
    <h2>{children}</h2>
    <h4>{info}</h4>
  </div>
);

const TitleSubtitleNoInfoComponent = React.createElement(
  TitleSubtitleInfo,
  { title: "Transforming Elements" },
  "Rendering more content"
);

How can we add info to our props? Let's clone our existing TitleSubtitleNoInfoComponent component and pass in info via props.

const TitleSubtitleInfoComponent = React.cloneElement(
  TitleSubtitleNoInfoComponent,
  { info: "Additional Info" }
);

render(TitleSubtitleInfoComponent);

As we can see, we are able to display the additional info. cloneElement gives us the capability to extend a React element with additional props when needed. It's important to note that we can achieve the same via:

// https://reactjs.org/docs/react-api.html#cloneelement
<element.type {...element.props} {...props}>{children}</element.type>

This also means, we can override any children, via defining the third argument. We will keep this in mind, when building an actual example in the next section.

Aside from creating and transforming single elements, the React API also exposes React.Children, a collection of utility functions for working with children elements.

What if we need to verify that we only have a single child element? This is where only comes handy.

const { count, forEach, map, only, toArray } = React.Children;

// helper class
class Verify extends React.Component {
  render() {
    return (
      <div>
        <h2>Verify: {this.props.title}</h2>
        Result:
        <div>{this.props.verify(this.props.children)}</div>
      </div>
    );
  }
}

If we pass in a collection or a non React Element, only will throw an error. This can help us to ensure that we actually only have single Element, which can be useful when creating a library and needing to enforce this constraint.

render(
  <Verify title="Single Element" verify={c => (only(c) ? "True" : "False")}>
    <div>Single React Element</div>
  </Verify>
);

There might be a need to count the number of children, which we can do via the count function. A simple length check might fail because we can't guarantee that children are of type array.

const Element = ({ id }) => <div key={id}>Element {id}</div>;

render(
  <Verify
    title="Should contain 3 Elements"
    verify={c => (count(c) === 3 ? "True" : "False")}
  >
    <Element id="1" />
    <Element id="2" />
    <Element id="3" />
  </Verify>
);

Further more React.Children offers two different ways to iterate over a children collection: map and forEach.

They function just as the Array based map and forEach. map returns a new collection, except in cases where children is null or undefined (returning null or respectively undefined) while forEach is the imperative counterpart.

To extend on our existing example, let's try to an additional property by mapping over a child collection and adding a new prop name.

const ElementAdditionalProp = ({ id, name }) => (
  <div key={id} id={id}>
    Element:{name} {id}
  </div>
);

In the following example we map over the collection and clone every single element and merge the new prop name with the existing props.

render(
  <Verify
    title="Map Multiple Elements"
    verify={c => map(c, El => React.cloneElement(El, { name: "Mapped" }))}
  >
    <ElementAdditionalProp id="1" />
    <ElementAdditionalProp id="2" />
    <ElementAdditionalProp id="3" />
  </Verify>
);

Here is the same example like the previous one, with one difference, we use forEach to achieve the same outcome.

render(
  <Verify
    title="ForEach Mulitple Elements"
    verify={c => {
      let elements = [];
      forEach(c, El =>
        elements.push(React.cloneElement(El, { name: "ForEach" }))
      );
      return elements;
    }}
  >
    <ElementAdditionalProp id="1" key="1" />
    <ElementAdditionalProp id="2" key="2" />
    <ElementAdditionalProp id="3" key="3" />
  </Verify>
);

There will be situations where we might need to access to the actual children array instead of the opaque data structure. toArray enables us to convert the actual child list to a flatted list.

render(
  <Verify
    title="toArray and filter Multiple Elements"
    verify={c => {
      const elements = toArray(c);
      return elements.filter(El => El.props.points > 100);
    }}
  >
    <ElementAdditionalProp id="1" key="1" points={100} />
    <ElementAdditionalProp id="2" key="2" points={200} />
    <ElementAdditionalProp id="3" key="3" points={300} />
  </Verify>
);

Example

Now that we have a basic understanding of how to create and transform elements and which utilities are available for helping us with these transformations, let's build something useful.

We will build a <Pipe /> component for composing multiple components that all use a render-callback approach. A render-callback is leveraged by the following components for example, where children is a function.

class AddX extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: this.props.initX || 0 };
  }
  render() {
    const { initX, ...props } = this.props;
    return this.props.children({ ...this.state, ...props });
  }
}

class AddY extends React.Component {
  constructor(props) {
    super(props);
    this.state = { y: this.props.initY || 0 };
  }
  render() {
    const { initY, ...props } = this.props;
    return this.props.children({ ...this.state, ...props });
  }
}

Let's quickly see how this might work composing multiple of these components.

const ComposedRenderCallbacks = () => (
  <AddX initX={10} additionalProp="Testing Composition">
    {props => (
      <AddY {...props} initY={20}>
        {({ x, y, additionalProp }) => (
          <div>
            <h4>Additional Prop: {additionalProp}</h4>
            X: {x}
            <br />
            Y: {y}
          </div>
        )}
      </AddY>
    )}
  </AddX>
);

render(<ComposedRenderCallbacks />);

We can verify that composition works by nesting components. Our component should enable to compose multiple components similar to a regular pipe function without having to keep nesting. So our high level goal is to offer a pipe(comp1, comp2, comp3, ..., viewComp) via JSX.

Let's think about how this might look in JSX. Here is a possible outcome:

<Pipe info="Pipe Function Rendering X and Y">
  <AddX initX={10} />
  <AddY initY={20} />
  {({ x, y, info }) => (
    <div>
      <h4>Info: {info}</h4>
      X: {x}
      <br />
      Y: {y}
    </div>
  )}
</Pipe>

Just by looking at the above example, we can see that we want to be able to pipe props and pass them to the final render callback function.

First of all, we need to make sure that only valid elements are composed together, further more we also need to make sure that the final function is actually a function. Let's handle the simplest case: we only have a single function.

function Pipe(elements, collectedProps = {}) {
  if (typeof elements === "function") {
    return elements(collectedProps);
  }
  // handle multiple elements
}

// verify
render(
  <Pipe title="Verify that Pipe1 works">
    {({ title }) => <h3>{title}</h3>}
  </Pipe>
);

Our next step is to handle multiple elements. By destructuring the elements collection, we can check if we have any elements left aside from the first, and if not, check if the only element is a function. If so, we can call the function with the collected props and return the result.

function Pipe(elements, collectedProps = {}) {
  if (typeof elements === "function") {
    return elements(collectedProps);
  }

  const [element, ...rest] = elements;
  if (rest.length === 0 && typeof element === "function") {
    return element(collectedProps);
  }

  // handle multiple elements
}

In case the element is not a function, we need to check if the element is a valid React element and either create or clone the element. We have already seen this concept in the introduction part.

function Pipe(elements, collectedProps = {}) {
  if (typeof elements === "function") {
    return elements(collectedProps);
  }

  const [element, ...rest] = elements;
  if (rest.length === 0 && typeof element === "function") {
    return element(collectedProps);
  }

  const createElement = React.isValidElement(element)
    ? React.cloneElement
    : React.createElement;

  // override the existing children function
}

To enable to collect the next props we need to a way to override the current children function, to call our Pipe function until we reach the last function.

function children({ children, ...props }) {
  const nextProps = Object.assign({}, collectedProps, props);
  return Pipe(rest, nextProps);
}

What is left is to override the children function via our previously defined createElement function.

function Pipe({ children: elements, ...collectedProps }) {
  if (typeof elements === "function") {
    return elements(collectedProps);
  }

  const [element, ...rest] = elements;
  if (rest.length === 0 && typeof element === "function") {
    return element(collectedProps);
  }

  const createElement = React.isValidElement(element)
    ? React.cloneElement
    : React.createElement;

  function children({ children, ...props }) {
    const nextProps = Object.assign({}, collectedProps, props);
    return Pipe({ children: rest, ...nextProps });
  }

  return createElement(element, {}, children);
}

We have a working Pipe function, what is left is to validate if our function actually works.

render(
  <Pipe info="Pipe Function Rendering X and Y">
    <AddX initX={10} />
    <AddY initY={20} />
    {({ x, y, info }) => (
      <div>
        <h4>Info: {info}</h4>
        X: {x}
        <br />
        Y: {y}
      </div>
    )}
  </Pipe>
);

As seen we are able to collect all needed props and pass it the callback function, that does the actual rendering.

We should have a solid understanding how to transform elements in React now.

React's top level API offers very useful utilities to help us with this. For more details consult the official React documentation.

If you have any questions or feedback please connect via Twitter: A. Sharif

// Transforming Elements in React
// Introduction
// Transforming elements can be interesting if you're a library author, but can also be very valuable when
// writing components to abstract behaviour in your existing code base.
//
// To get a better understanding, we will walkthrough the most important
// React Top Level API transformation functionalities.
// React offers a couple of helper functions that we can leverage when for creating and adapting elements,
// further information is available via the official React documentation.
// https://reactjs.org/docs/react-api.html
//
// Let's take a closer look at what we can take-away in the first section and then build a component
// that leverages the newly learnt features.
// Basics
import React from "react";
import ReactDOM from "react-dom";
// `render` is our main function, which we will use to render all our examples.
const render = Component =>
ReactDOM.render(Component, document.getElementById("root"));
// Let's build a Title element and a stateless component called App.
const Title = <h1>Transforming Elements</h1>;
const App = () => <div>{Title}</div>;
// [uncomment]
// console.log("Title is valid?", React.isValidElement(Title)); // => true
// console.log("App is valid?", React.isValidElement(App)); // => false
// With the help of `isValidElement` we can check, as the name already implies, if
// the element is a React element or not.
// The following checks validate that `Title` is an actual React element, while `App` itself is not,
// as it's a function. When calling `App` we get a valid element back,
// which can be verified via the next `isValidElement` check.
// [uncomment]
// console.log("<App /> is valid?", React.isValidElement(<App />)); // => true
// What could be a useful case for needing these checks?
// For example if we need to know if we have to create or clone an element.
// Which brings us to the next interesting feature, `createElement` and respectively
// `cloneElement`.
// Even if you're wondering what React.createElement is, you've actually already used this function,
// even if unknowninly.
// When we wrote <h1>Transforming Elements</h1>
// what actually happened was that this JSX definition was converted to:
// React.createElement("h1", {}, "Transforming Elements")
//
// Let's build the exact definition and render it to the screen.
const TitleH1 = React.createElement("h1", null, "Transforming Elements");
// [uncomment]
// render(TitleH1);
// We can already verify that this is working as expected.
// Let's take a closer look at the `createElement` arguments.
//
// `React.createElement(type, [props], [...children])`
//
// As we can see from the above definition, we need a valid type,
// but what happens when we try to invoke it with an invalid element?
const TitleTest = React.createElement("test", null, "Transforming Elements");
// [uncomment]
// render(TitleTest);
// A console warning is logged:
//
// ```
// Warning: The tag <test> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
// ```
//
// So React can tell when we're passing in an invalid node type.
// But the warning also reveils another interesting information:
//
// ```
// If you meant to render a React component, start its name with an uppercase letter.
// ```
//
// This message indicates that we can pass in a React component as the first argument.
// Let's try this out.
const TitleSubtitle = ({ children, title }) => (
<div>
<h1>{title}</h1>
<h2>{children}</h2>
</div>
);
const SubtitleComponent = React.createElement(
TitleSubtitle,
null,
"Rendering more content"
);
// [uncomment]
// render(SubtitleComponent);
// We can see that the children content is rendered into the H2 tag, but how can we define the title?
// If you recall, createElement accepts props optionally as a second argument.
// So our next step is to define the title we want displayed.
const TitleSubtitleComponent = React.createElement(
TitleSubtitle,
{ title: "Transforming Elements" },
"Rendering more content"
);
// [uncomment]
// render(TitleSubtitleComponent);
// We can verify that this works.
// Now that we have a basic understanding of what happens when we use JSX, let's take a look at another useful React top level API feature.
//
// `cloneElement` has the following definition:
//
// ```
// React.cloneElement(element, [props], [...children])
// ```
//
// At first glance, this looks very similar to the previously explained `createElement`,
// with one important distinction, it expects a valid React element.
// So, let's go back to `isValidElement` again and verify that our current component
// actually works as expected.
// [uncomment]
// console.log(
// "TitleSubtitleComponent is valid?",
// React.isValidElement(TitleSubtitleComponent)
// ); // => true
// We already know that we have a valid element, otherwise we wouldn't have been able to previously render the component,
// but also gave us another opportunity to run `isValidElement` one more time.
// Next let's assume we have a third value we want to display inside our component.
const TitleSubtitleInfo = ({ children, title, info }) => (
<div>
<h1>{title}</h1>
<h2>{children}</h2>
<h4>{info}</h4>
</div>
);
const TitleSubtitleNoInfoComponent = React.createElement(
TitleSubtitleInfo,
{ title: "Transforming Elements" },
"Rendering more content"
);
// How can we add `info` to our props?
// Let's clone our existing `TitleSubtitleNoInfoComponent` component and pass in
// `info` via props.
const TitleSubtitleInfoComponent = React.cloneElement(
TitleSubtitleNoInfoComponent,
{ info: "Additional Info" }
);
// [uncomment]
// render(TitleSubtitleInfoComponent);
// As we can see, we are able to display the additional info.
// `cloneElement` gives us the capability to extend a React element with additional props when needed.
// It's important to note that we can achieve the same via:
//
// ```
// // https://reactjs.org/docs/react-api.html#cloneelement
// <element.type {...element.props} {...props}>{children}</element.type>
// ```
//
// This also means, we can override any children, via defining the third argument.
// We will keep this in mind, when building an actual example in the next section.
//
// Aside from creating and transforming single elements, the React API also exposes
// `React.Children`, a collection of utility functions for working with children elements.
//
// What if we need to verify that we only have a single child element?
// This is where `only` comes handy.
const { count, forEach, map, only, toArray } = React.Children;
// helper class
class Verify extends React.Component {
render() {
return (
<div>
<h2>Verify: {this.props.title}</h2>
Result:
<div>{this.props.verify(this.props.children)}</div>
</div>
);
}
}
// If we pass in a collection or a non React Element, `only` will throw an error.
// This can help us to ensure that we actually only have single Element, which can
// be useful when creating a library and needing to enforce this constraint.
// [uncomment]
// render(
// <Verify title="Single Element" verify={c => (only(c) ? "True" : "False")}>
// <div>Single React Element</div>
// </Verify>
// );
// There might be a need to count the number of children, which we can do via the `count` function.
// A simple length check might fail because we can't guarantee that children are of type `array`.
const Element = ({ id }) => <div key={id}>Element {id}</div>;
// [uncomment]
// render(
// <Verify
// title="Should contain 3 Elements"
// verify={c => (count(c) === 3 ? "True" : "False")}
// >
// <Element id="1" />
// <Element id="2" />
// <Element id="3" />
// </Verify>
// );
// Further more `React.Children` offers two different ways to iterate over a children collection: `map` and `forEach`.
//
// They function just as the `Array` based `map` and `forEach`. `map` returns a new collection,
// except in cases where children is `null` or `undefined` (returning `null` or respectively `undefined`)
// while `forEach` is the imperative counterpart.
//
// To extend on our existing example, let's try to an additional property by mapping
// over a child collection and adding a new prop name.
const ElementAdditionalProp = ({ id, name }) => (
<div key={id} id={id}>
Element:{name} {id}
</div>
);
// In the following example we map over the collection and clone every single element and merge the new prop `name`
// with the existing props.
// [uncomment]
// render(
// <Verify
// title="Map Multiple Elements"
// verify={c => map(c, El => React.cloneElement(El, { name: "Mapped" }))}
// >
// <ElementAdditionalProp id="1" />
// <ElementAdditionalProp id="2" />
// <ElementAdditionalProp id="3" />
// </Verify>
// );
// Here is the same example like the previous one, with one difference, we use `forEach`
// to achieve the same outcome.
// [uncomment]
// render(
// <Verify
// title="ForEach Multpile Elements"
// verify={c => {
// let elements = [];
// forEach(c, El =>
// elements.push(React.cloneElement(El, { name: "ForEach" }))
// );
// return elements;
// }}
// >
// <ElementAdditionalProp id="1" key="1" />
// <ElementAdditionalProp id="2" key="2" />
// <ElementAdditionalProp id="3" key="3" />
// </Verify>
// );
// There will be situations where we might need to access to the actual children array instead
// of the opaque data structure. `toArray` enables us to convert the actual child list
// to a flatted list.
// [uncomment]
// render(
// <Verify
// title="toArray and filter Multiple Elements"
// verify={c => {
// const elements = toArray(c);
// return elements.filter(El => El.props.points > 100);
// }}
// >
// <ElementAdditionalProp id="1" key="1" points={100} />
// <ElementAdditionalProp id="2" key="2" points={200} />
// <ElementAdditionalProp id="3" key="3" points={300} />
// </Verify>
// );
// Example
// Now that we have a basic understanding of how to create and transform elements
// and which utilities are available for helping us with these transformations, let's build
// something useful.
//
// We will build a `<Pipe />` component for composing multiple components that all
// use a render-callback approach.
// A render-callback is leveraged by the following components for example, where children is a function.
class AddX extends React.Component {
constructor(props) {
super(props);
this.state = { x: this.props.initX || 0 };
}
render() {
const { initX, ...props } = this.props;
return this.props.children({ ...this.state, ...props });
}
}
class AddY extends React.Component {
constructor(props) {
super(props);
this.state = { y: this.props.initY || 0 };
}
render() {
const { initY, ...props } = this.props;
return this.props.children({ ...this.state, ...props });
}
}
// Let's quickly see how this might work composing multiple of these components.
const ComposedRenderCallbacks = () => (
<AddX initX={10} additionalProp="Testing Composition">
{props => (
<AddY {...props} initY={20}>
{({ x, y, additionalProp }) => (
<div>
<h4>Additional Prop: {additionalProp}</h4>
X: {x}
<br />
Y: {y}
</div>
)}
</AddY>
)}
</AddX>
);
// [uncomment]
// render(<ComposedRenderCallbacks />);
// We can verify that composition works by nesting components.
// Our component should enable to compose multiple components similar to
// a regular pipe function without having to keep nesting.
// So our high level goal is to offer a `pipe(comp1, comp2, comp3, ..., viewComp)` via JSX.
// Let's think about how this might look in JSX. Here is a possible outcome:
//
// ```
// <Pipe info="Pipe Function Rendering X and Y">
// <AddX initX={10} />
// <AddY initY={20} />
// {({ x, y, info }) => (
// <div>
// <h4>Info: {info}</h4>
// X: {x}
// <br />
// Y: {y}
// </div>
// )}
// </Pipe>
// ```
// Just by looking at the above example, we can see that we want to be able to pipe props and pass
// them to the final render callback function.
//
// First of all, we need to make sure that only valid elements are composed together, further more
// we also need to make sure that the final function is actually a function.
// Let's handle the simplest case: we only have a single function.
function Pipe1({ children: elements, ...collectedProps }) {
if (typeof elements === "function") {
return elements(collectedProps);
}
// handle multiple elements
}
// [uncomment]
// render(
// <Pipe1 title="Verify that Pipe1 works">
// {({ title }) => <h3>{title}</h3>}
// </Pipe1>
// );
// Our next step is to handle multiple elements.
// By destructuring the elements collection, we can check if we have any
// elements left aside from the first, and if not, check if the only element is a function.
// If so, we can call the function with the collected props and return the result.
function Pipe2({ children: elements, ...collectedProps }) {
if (typeof elements === "function") {
return elements(collectedProps);
}
const [element, ...rest] = elements;
if (rest.length === 0 && typeof element === "function") {
return element(collectedProps);
}
// handle multiple elements
}
// In case the element is not a function, we need to check if the element is
// a valid React element and either create or clone the element. We have already
// seen this concept in the introduction part.
function Pipe3(elements, collectedProps = {}) {
if (typeof elements === "function") {
return elements(collectedProps);
}
const [element, ...rest] = elements;
if (rest.length === 0 && typeof element === "function") {
return element(collectedProps);
}
const createElement = React.isValidElement(element)
? React.cloneElement
: React.createElement;
// override the existing children function
}
// To enable to collect the next props we need to a way to override the current
// children function, to call our `Pipe` function until we reach the last function.
// ```
// function children({ children, ...props }) {
// const nextProps = Object.assign({}, collectedProps, props);
// return Pipe(rest, nextProps);
// }
// ```
// What is left is to override the children function via our previously defined `createElement` function.
function Pipe({ children: elements, ...collectedProps }) {
if (typeof elements === "function") {
return elements(collectedProps);
}
const [element, ...rest] = elements;
if (rest.length === 0 && typeof element === "function") {
return element(collectedProps);
}
const createElement = React.isValidElement(element)
? React.cloneElement
: React.createElement;
function children({ children, ...props }) {
const nextProps = Object.assign({}, collectedProps, props);
return Pipe({ children: rest, ...nextProps });
}
return createElement(element, {}, children);
}
// We have a working `Pipe` function, what is left is to validate if our function actually works.
// [uncomment]
// render(
// <Pipe info="Pipe Function Rendering X and Y">
// <AddX initX={10} />
// <AddY initY={20} />
// {({ x, y, info }) => (
// <div>
// <h4>Info: {info}</h4>
// X: {x}
// <br />
// Y: {y}
// </div>
// )}
// </Pipe>
// );
// As seen we are able to collect all needed props and pass it the callback function,
// that does the actual rendering.
// We should have a solid understanding how to transform elements in React now.
// React's top level API offers very useful utilities to help us with this.
// For more details consult the official React documentation. https://reactjs.org/docs/react-api.html
// If you have any questions or feedback please connect via Twitter:
// A. Sharif : https://twitter.com/sharifsbeat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment