Detailed explanation of the use of React.cloneElement

Detailed explanation of the use of React.cloneElement

Because we have to take over the maintenance of some projects, the team's technology stack has recently switched from Vue to React. As a React novice, and because I always like to learn new things through source code, I chose to learn some usage of React by reading the source code of the famous project antd.

While reading the source code, I found that many components used the React.cloneElement API. Although I could guess what it did from the name, I didn't know its specific function. Then I went to read the official documentation, which clearly described its function, but did not tell us in what scenarios we need to use it. So I summarized some usage scenarios based on the description in the document, combined with the use of source code, and oriented to Google and stackoverflow.

The role of cloneElement

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

First, take a look at the official documentation of this API:

Clone and return a new React element using element as the starting point. The resulting element will have the original element's props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved.

To sum up:

  1. Clone the original element and return a new React element;
  2. Keep the props of the original element, and add new props, and merge the two shallowly;
  3. key and ref will be retained, because they are also props, so they can also be modified;
  4. According to the source code of react, we can define as many child elements as we want starting from the third parameter. If new children are defined, the original children will be replaced.

Usage scenarios

According to the above definition, we can use this API as needed in different scenarios.

Adding new props

When we create a common component, we want to add different class names to each child element according to the internal logic. At this time, we can modify its className :

Suppose we have a Timeline component that allows us to define multiple TimelineItem as needed. Internally, we want to add a timeline-item-last class to the last TimelineItem to render a special effect. At this time, we can do this:

const MyTimeline = () => {
 return (
  <Timeline>
   <TimelineItem>2020-06-01</TimelineItem>
   <TimelineItem>2020-06-08</TimelineItem>
   <TimelineItem>2020-07-05</TimelineItem>
  </Timeline>
 )
}

// Inside the Timeline, the logic might be like this import class from 'classnames';
const Timeline = props => {
 // ...
 // ...
 const itemCount = React.children.count(props.children);
 const items = React.children.map(props.children, (item, index) => {
  return React.cloneElement(item, {
   className: class([
    item.props.className,
    'timeline-item',
    index === count - 1 ? 'timeline-item-last' : ''
   ])
  })
 }
 return <div className={'timeline'}>{ items }</div>
}

In addition to adding className , you can also dynamically add more props information to subcomponents. react-router 's Switch will add location and computedMatch information to matching subcomponents:

class Switch extends React.Component {
 render() {
  return (
   <RouterContext.Consumer>
    {context => {
     invariant(context, "You should not use <Switch> outside a <Router>");

     const location = this.props.location || context.location;

     let element, match;

     // We use React.Children.forEach instead of React.Children.toArray().find()
     // here because toArray adds keys to all child elements and we do not want
     // to trigger an unmount/remount for two <Route>s that render the same
     // component at different URLs.
     React.Children.forEach(this.props.children, child => {
      if (match == null && React.isValidElement(child)) {
       element = child;

       const path = child.props.path || child.props.from;

       match = path
        ? matchPath(location.pathname, { ...child.props, path })
        : context.match;
      }
     });

     return match
      React.cloneElement(element, { location, computedMatch: match })
      : null;
    }}
   </RouterContext.Consumer>
  );
 }
}

Events that modify props

Suppose we have a Tab component, which contains multiple TabPane subcomponents. We want to trigger the Tab's onClick event when clicking each TabPane subcomponent. The user may define an independent onClick event for each TabPane. In this case, we need to modify the subcomponent onClick event:

const Tab = props => {
 const { onClick } = props;
 const tabPanes = React.children.map(props.children, (tabPane, index) => {
  const paneClick = () => {
   onClick && onClick(index);
   tabPane.props?.onClick();
  }
  return React.cloneElement(tabPane, {
    onClick: paneClick,
  })
 })
 return <div>{ tabPanes }</div>
}

Custom Style

When creating a component called FollowMouse , we allow users to define the content component Content . When the mouse moves, the position of Content is automatically calculated according to the size of the content to avoid overflowing the screen. At this time, we can use cloneElement to dynamically modify its style.

// For simplicity, mouse events are omitted here.
const FollowMouse = props => {
 const { Content } = props;
 const customContent = React.isValidElement ? Content : <span>{ Content }</span>
 const getOffset = () => {
  return {
   position: 'absolute',
   top: ...,
   left: ...,
  }
 }
 const renderContent = React.cloneElement(custonContent, {
  style: {
   ...getOffset()
  }
 })
 return <div>{ renderContent() }</div>
}

Add key

When we create a list of elements, we can add a key to each node through cloneElement .

const ComponentButton = props => {
 const { addonAfter, children } = props;
 const button = <button key='button'>{ children }</button>
 const list = [button, addonAfter ? React.cloneElement(addonAfter, { key: 'button-addon' } : null)
 return <div>{ list } <div>
}

Summarize

When developing complex components, we often add different functions or display effects to child components as needed. The react element itself is an immutable object. props.children is not actually children themselves, but just the descriptor of children . We cannot modify any of its properties, but can only read its contents. Therefore, React.cloneElement allows us to copy its elements and modify or add new props to achieve our goals.

Of course, thanks to the powerful combination mode of react, this is not limited to props.children . Whether it is props.left or props.right or any other props passed in, as long as it is a legal react element, we can use this React.cloneElement to operate on it.

The above is the detailed content of the detailed explanation of the use of React.cloneElement. For more information about the use of React.cloneElement, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Detailed usage of React.Children
  • React Hooks Usage Examples
  • In-depth understanding of the core principles of React Native (Bridge of React Native)
  • React+Koa example of implementing file upload
  • Example code for developing h5 form page based on react hooks and zarm component library configuration
  • React antd tabs switching causes repeated refresh of subcomponents
  • ReactJs Basics Tutorial - Essential Edition
  • ReactRouter implementation

<<:  How to install nginx in docker and configure access via https

>>:  Detailed explanation of binary and varbinary data types in MySQL

Recommend

Two solutions for automatically adding 0 to js regular format date and time

Table of contents background Solution 1 Ideas: Co...

How to use environment variables in nginx configuration file

Preface Nginx is an HTTP server designed for perf...

React gets input value and submits 2 methods examples

Method 1: Use the target event attribute of the E...

Detailed explanation of the use of find_in_set() function in MySQL

First, let’s take an example: There is a type fie...

Nginx service 500: Internal Server Error one of the reasons

500 (Internal Server Error) The server encountere...

Example code for implementing 3D Rubik's Cube with CSS

Let's make a simple 3D Rubik's Cube today...

JavaScript implements bidirectional linked list process analysis

Table of contents 1. What is a doubly linked list...

Comparative Analysis of MySQL Binlog Log Processing Tools

Table of contents Canal Maxwell Databus Alibaba C...

Use iframe to display weather effects on web pages

CSS: Copy code The code is as follows: *{margin:0;...

CentOS7 upgrade kernel kernel5.0 version

Upgrade process: Original system: CentOS7.3 [root...

Install Python virtual environment in Ubuntu 18.04

For reference only for Python developers using Ub...

Detailed explanation based on event bubbling, event capture and event delegation

Event bubbling, event capturing, and event delega...

Example method of deploying react project on nginx

Test project: react-demo Clone your react-demo pr...