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

$nextTick explanation that you can understand at a glance

Table of contents 1. Functional description 2. Pa...

Sharing ideas on processing tens of millions of data in a single MySQL table

Table of contents Project Background Improvement ...

How to use CSS custom variables in Vue

Table of contents The CSS custom variable functio...

Example of using store in vue3 to record scroll position

Table of contents Overall Effect Listen for conta...

Use of Linux relative and absolute paths

01. Overview Absolute paths and relative paths ar...

How to dynamically add a volume to a running Docker container

Someone asked me before whether it is possible to...

Specific use of MySQL operators (and, or, in, not)

Table of contents 1. Introduction 2. Main text 2....

Web page custom selection box Select

Everyone may be familiar with the select drop-dow...

Two ways to implement square div using CSS

Goal: Create a square whose side length is equal ...

CentOS7 uses rpm package to install mysql 5.7.18

illustrate This article was written on 2017-05-20...

JavaScript gets the scroll bar position and slides the page to the anchor point

Preface This article records a problem I encounte...

Centos7 installation of Nginx integrated Lua sample code

Preface The computer I use is a Mac, and the oper...

Pull-down refresh and pull-up loading components based on Vue encapsulation

Based on Vue and native javascript encapsulation,...

A brief summary of basic web page performance optimization rules

Some optimization rules for browser web pages Pag...

jQuery implements a simple comment area

This article shares the specific code of jQuery t...