Movatterモバイル変換


[0]ホーム

URL:


Is this page useful?

Pitfall

UsingcloneElement is uncommon and can lead to fragile code.See common alternatives.

cloneElement lets you create a new React element using another element as a starting point.

constclonedElement =cloneElement(element,props,...children)

Reference

cloneElement(element, props, ...children)

CallcloneElement to create a React element based on theelement, but with differentprops andchildren:

import{cloneElement}from'react';

// ...
constclonedElement =cloneElement(
<Rowtitle="Cabbage">
Hello
</Row>,
{isHighlighted:true},
'Goodbye'
);

console.log(clonedElement);// <Row title="Cabbage" isHighlighted={true}>Goodbye</Row>

See more examples below.

Parameters

  • element: Theelement argument must be a valid React element. For example, it could be a JSX node like<Something />, the result of callingcreateElement, or the result of anothercloneElement call.

  • props: Theprops argument must either be an object ornull. If you passnull, the cloned element will retain all of the originalelement.props. Otherwise, for every prop in theprops object, the returned element will “prefer” the value fromprops over the value fromelement.props. The rest of the props will be filled from the originalelement.props. If you passprops.key orprops.ref, they will replace the original ones.

  • optional...children: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers,portals, empty nodes (null,undefined,true, andfalse), and arrays of React nodes. If you don’t pass any...children arguments, the originalelement.props.children will be preserved.

Returns

cloneElement returns a React element object with a few properties:

  • type: Same aselement.type.
  • props: The result of shallowly mergingelement.props with the overridingprops you have passed.
  • ref: The originalelement.ref, unless it was overridden byprops.ref.
  • key: The originalelement.key, unless it was overridden byprops.key.

Usually, you’ll return the element from your component or make it a child of another element. Although you may read the element’s properties, it’s best to treat every element as opaque after it’s created, and only render it.

Caveats

  • Cloning an elementdoes not modify the original element.

  • You should onlypass children as multiple arguments tocloneElement if they are all statically known, likecloneElement(element, null, child1, child2, child3). If your children are dynamic, pass the entire array as the third argument:cloneElement(element, null, listItems). This ensures that React willwarn you about missingkeys for any dynamic lists. For static lists this is not necessary because they never reorder.

  • cloneElement makes it harder to trace the data flow, sotry thealternatives instead.


Usage

Overriding props of an element

To override the props of someReact element, pass it tocloneElement with theprops you want to override:

import{cloneElement}from'react';

// ...
constclonedElement =cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);

Here, the resultingcloned element will be<Row title="Cabbage" isHighlighted={true} />.

Let’s walk through an example to see when it’s useful.

Imagine aList component that renders itschildren as a list of selectable rows with a “Next” button that changes which row is selected. TheList component needs to render the selectedRow differently, so it clones every<Row> child that it has received, and adds an extraisHighlighted: true orisHighlighted: false prop:

exportdefaultfunctionList({children}){
const[selectedIndex,setSelectedIndex] =useState(0);
return(
<divclassName="List">
{Children.map(children,(child,index)=>
cloneElement(child,{
isHighlighted:index ===selectedIndex
})
)}

Let’s say the original JSX received byList looks like this:

<List>
<Rowtitle="Cabbage"/>
<Rowtitle="Garlic"/>
<Rowtitle="Apple"/>
</List>

By cloning its children, theList can pass extra information to everyRow inside. The result looks like this:

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

Notice how pressing “Next” updates the state of theList, and highlights a different row:

Fork
import{Children,cloneElement,useState}from'react';exportdefaultfunctionList({children}){const[selectedIndex,setSelectedIndex] =useState(0);return(<divclassName="List">{Children.map(children,(child,index)=>cloneElement(child,{isHighlighted:index ===selectedIndex}))}<hr/><buttononClick={()=>{setSelectedIndex(i=>(i +1) %Children.count(children));}}>        Next</button></div>);}

To summarize, theList cloned the<Row /> elements it received and added an extra prop to them.

Pitfall

Cloning children makes it hard to tell how the data flows through your app. Try one of thealternatives.


Alternatives

Passing data with a render prop

Instead of usingcloneElement, consider accepting arender prop likerenderItem. Here,List receivesrenderItem as a prop.List callsrenderItem for every item and passesisHighlighted as an argument:

exportdefaultfunctionList({items,renderItem}){
const[selectedIndex,setSelectedIndex] =useState(0);
return(
<divclassName="List">
{items.map((item,index)=>{
constisHighlighted =index ===selectedIndex;
returnrenderItem(item,isHighlighted);
})}

TherenderItem prop is called a “render prop” because it’s a prop that specifies how to render something. For example, you can pass arenderItem implementation that renders a<Row> with the givenisHighlighted value:

<List
items={products}
renderItem={(product,isHighlighted)=>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>

The end result is the same as withcloneElement:

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

However, you can clearly trace where theisHighlighted value is coming from.

Fork
import{useState}from'react';exportdefaultfunctionList({items,renderItem}){const[selectedIndex,setSelectedIndex] =useState(0);return(<divclassName="List">{items.map((item,index)=>{constisHighlighted =index ===selectedIndex;returnrenderItem(item,isHighlighted);})}<hr/><buttononClick={()=>{setSelectedIndex(i=>(i +1) %items.length);}}>        Next</button></div>);}

This pattern is preferred tocloneElement because it is more explicit.


Passing data through context

Another alternative tocloneElement is topass data through context.

For example, you can callcreateContext to define aHighlightContext:

exportconstHighlightContext =createContext(false);

YourList component can wrap every item it renders into aHighlightContext provider:

exportdefaultfunctionList({items,renderItem}){
const[selectedIndex,setSelectedIndex] =useState(0);
return(
<divclassName="List">
{items.map((item,index)=>{
constisHighlighted =index ===selectedIndex;
return(
<HighlightContextkey={item.id}value={isHighlighted}>
{renderItem(item)}
</HighlightContext>
);
})}

With this approach,Row does not need to receive anisHighlighted prop at all. Instead, it reads the context:

exportdefaultfunctionRow({title}){
constisHighlighted =useContext(HighlightContext);
// ...

This allows the calling component to not know or worry about passingisHighlighted to<Row>:

<List
items={products}
renderItem={product=>
<Rowtitle={product.title}/>
}
/>

Instead,List andRow coordinate the highlighting logic through context.

Fork
import{useState}from'react';import{HighlightContext}from'./HighlightContext.js';exportdefaultfunctionList({items,renderItem}){const[selectedIndex,setSelectedIndex] =useState(0);return(<divclassName="List">{items.map((item,index)=>{constisHighlighted =index ===selectedIndex;return(<HighlightContextkey={item.id}value={isHighlighted}>{renderItem(item)}</HighlightContext>);})}<hr/><buttononClick={()=>{setSelectedIndex(i=>(i +1) %items.length);}}>        Next</button></div>);}

Learn more about passing data through context.


Extracting logic into a custom Hook

Another approach you can try is to extract the “non-visual” logic into your own Hook, and use the information returned by your Hook to decide what to render. For example, you could write auseList custom Hook like this:

import{useState}from'react';

exportdefaultfunctionuseList(items){
const[selectedIndex,setSelectedIndex] =useState(0);

functiononNext(){
setSelectedIndex(i=>
(i +1) %items.length
);
}

constselected =items[selectedIndex];
return[selected,onNext];
}

Then you could use it like this:

exportdefaultfunctionApp(){
const[selected,onNext] =useList(products);
return(
<divclassName="List">
{products.map(product=>
<Row
key={product.id}
title={product.title}
isHighlighted={selected ===product}
/>
)}
<hr/>
<buttononClick={onNext}>
Next
</button>
</div>
);
}

The data flow is explicit, but the state is inside theuseList custom Hook that you can use from any component:

Fork
importRowfrom'./Row.js';importuseListfrom'./useList.js';import{products}from'./data.js';exportdefaultfunctionApp(){const[selected,onNext] =useList(products);return(<divclassName="List">{products.map(product=><Rowkey={product.id}title={product.title}isHighlighted={selected ===product}/>)}<hr/><buttononClick={onNext}>        Next</button></div>);}

This approach is particularly useful if you want to reuse this logic between different components.



[8]ページ先頭

©2009-2025 Movatter.jp