How and when does React.cloneElement() make use of the cloned component's "constructor"?

150 Views Asked by At

Recently I have been working my way through the Advanced React section of Coursera's Meta Front-End Developer Professional Certificate, and at some point we are tasked with creating a radio button list in the following manner:

src/App.js
import "./App.css";
import { RadioGroup, RadioOption } from "./Radio";
import { useState } from "react";

function App() {
  const [selected, setSelected] = useState("");
  return (
    <div className="App">
      <h2>How did you hear about Little Lemon?</h2>
      <RadioGroup onChange={setSelected} selected={selected}>
        <RadioOption value="social_media">Social Media</RadioOption>
        <RadioOption value="friends">Friends</RadioOption>
        <RadioOption value="advertising">Advertising</RadioOption>
        <RadioOption value="other">Other</RadioOption>
      </RadioGroup>
      <button disabled={!selected}>Submit</button>
    </div>
  );
}

export default App;

src/Radio/index.js
import * as React from "react";

export const RadioGroup = ({ onChange, selected, children }) => { 
 const RadioOptions = React.Children.map(children, (child) => { 
   return React.cloneElement(child, { 
     onChange, 
     checked: child.props.value === selected, 
   }); 
 }); 
 return <div className="RadioGroup">{RadioOptions}</div>; 
}; 
 
export const RadioOption = ({ value, checked, onChange, children }) => { 
 return ( 
   <div className="RadioOption"> 
     <input 
       id={value} 
       type="radio" 
       name={value} 
       value={value} 
       checked={checked} 
       onChange={(e) => { 
         onChange(e.target.value); 
       }} 
     /> 
     <label htmlFor={value}>{children}</label> 
   </div> 
 ); 
}; 

Explanation:

Most of the code seems pretty straightforward, except for when the legacy APIs React.Children and React.cloneElement (both of whom the usage is currently discouraged by React Docs themselves) are used. However, since these are the two most important functionalities of the code (and indeed the APIs that Meta intended to teach us in the course), I will go through what I believe they are doing, in an effort to help any future coursemates who might stumble upon the same piece of code, and in the end my question itself will be much clearer. To any more experienced coders who might be reading this: please, I invite you to correct any mistakes I make on my attempt at an explanation.

  1. We start by creating a RadioGroup element whose children are various RadioOption elements. Since a component receives its children from its constructor, I presume that a component's children must be created before the component itself in order for them to be available for their injection inside of their parent component. This means that in this example, the RadioOption elements are created before the initialization of the RadioGroup element (which only makes sense, given that in the initialization of the RadioGroup element we already have a children object over which to iterate).

  2. From that point, we jump over to the creation of the RadioOption elements in src/Radio/index.js. Since only the value attribute has been provided on their initialization in src/App.js, what seems to happen is that only the attributes who do not depend on either the checked or the onChange parameters for their initialization are properly initialized, for to the properties that receive parameters that have not been provided, undefined is assigned, and React ignores all the properties to whom undefined is assigned. This means that by the end of this step, each RadioOption element will be a <div> with: an <input> tag with the attributes id, type, name and value; and a <label> tag with the attribute htmlFor.

  3. Once this is all done, we are ready to create our RadioGroup element. We start by creating a copy of its children object, which is composed of all the previously created RadioOption elements, applying a transformation to each of its elements, and saving the copied and subsequently modified array to a const called RadioOptions. Now, what interests me is how such transformation is done:

  4. Inside of the callback for the .map() method, we clone each RadioOption element and append to it two properties which it previously did not have, namely, onChange and checked.

  5. By the end of this process, the state setter function setSelected, that was passed into RadioGroup through means of its property onChange, has made its way from the parameter of the RadioGroup constructor and into the onChange property of each individual RadioOption through usage of the React.cloneElement. This means that the code onChange={(e) => {onChange(e.target.value); }} is effectively rendered onChange={(e) => {setSelected(e.target.value); }} on the final object.

And this is what I have been able to decipher so far.

Question 1:

I do not understand the following, though:

Looking online I have found many of examples like such (this one comes from the documentation itself):

import { cloneElement } from 'react';

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

console.log(clonedElement);

This is supposed to log the following result: <Row title="Cabbage" isHighlighted={true}>Goodbye</Row>.

As can be seen, the isHighlighted property was added even though there was no coded specification for how to deal with this value such as there was in our RadioOption component to deal with the checked and onChange values. Nevertheless, it was successfully appended to the tag's clone.

This made me realize: React.cloneElement can be used to append a property to a component independently of such a property's prior implementation.

However, it is also the case that in the code for the radio buttons list above, React.cloneElement took into account the behaviour specified in the component's body when appending the property it had been provided with.

So where do we draw the line? How do I know exactly when it is going to behave in a way and when in another? Can anyone walk me through the inner workings of this method more in-depth?


Question 2:

Moreover, when we look at the example just above, we see that the passing of { isHighlighted: true } as an argument to React.cloneElement was enough to create, in the final object, a property isHighlighted with the value true.

However, in our code, we need the line checked={checked} inside of the <input>tag for the code to work properly. I believe that it might be because in the documentation's example the appending was done to a simple <Row> tag, whilst in our code the appending takes place one level deeper than our RadioOption's <div>, on the <input> tag inside of that.

Is that correct? And at last: how could I make it so that I could mimic the working of the documentation's example code, i.e., passing everything that should be appended inside the cloneElement() call and not relying in passing it as an argument to the constructor of the RadioOption component?


Thank you very much in advance.

0

There are 0 best solutions below