Reputation: 11
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:
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;
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>
);
};
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.
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).
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
.
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:
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
.
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.
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?
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.
Upvotes: 0
Views: 404