How can I append or change the className prop with React.cloneElement?
When I use React.cloneElement I'm unable to change or append the className prop. I've searched for hours but I found nothing. React.Children.only or removing the spread don't change the behavior. It appear to be a bug, or a performance optimization feature?.
Expect html: <div class="parent"><div class="child other-class">testing...</div></div>
Result html: <div class="parent"><div class="child">testing...</div></div>
Class example:
class Parent extends React.Component {
render() {
return (
<div className={"parent"}>
{React.cloneElement(React.Children.only(this.props.children), {
...this.props.children.props,
className: `${this.props.children.props.className} other-class`,
})}
</div>
);
}
}
class Child extends React.Component {
render() {
return <div className={"child"}>{"testing..."}</div>;
}
}
Functional component example:
const Parent = ({ children }) => (
<div className={"parent"}>
{React.cloneElement(React.Children.only(children), {
...children.props,
className: `${children.props.className} other-class`,
})}
</div>
);
const Child = () => <div className={"child"}>{"testing..."}</div>;
const Parent = ({ children }) => (
<div className={"parent"}>
{React.cloneElement(React.Children.only(children), {
...children.props,
className: `${children.props.className} other-class`,
})}
</div>
);
const Child = () => <div className={"child"}>{"testing2..."}</div>;
ReactDOM.render(
<React.StrictMode>
<Parent>
<Child />
</Parent>
</React.StrictMode>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The problem is that you're never using className in Child, which is what you're manipulating in Parent. Child puts a className on a div, but that isn't Child's className, it's just a hardcoded one that Child puts on the div.
If you want Child to put that class on the div, you have to write the code to do that. Also, you don't need the spread, the props are merged. Finally, to get the original className, I'd use the result of calling Children.only, rather than going back to this.props.children (though that will work because only would throw if there weren't only one).
See comments:
class Parent extends React.Component {
render() {
// Get the `className` from the child after verifying there's only one
const child = React.Children.only(this.props.children);
const className = `${child.props.className} other-class`;
return (
<div className={"parent"}>
{React.cloneElement(child, {
// No need to spread previous props here
className,
})}
</div>
);
}
}
class Child extends React.Component {
render() {
// Use `className` from `Child`'s props
const className = (this.props.className || "") + " child";
return <div className={className}>{"testing..."}</div>;
}
}
// Note the `classname` on `Child`, to show that your code using
// `this.props.children.props.className`
ReactDOM.render(<Parent><Child className="original"/></Parent>, document.getElementById("root"));
.child {
color: red;
}
.child.other-class {
color: green;
}
.original {
font-style: italic;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Related
I need to put the change children with some element like div in react component. and I found lot of way to do that.
The problem is when I create a holder element over the child and give right key to the holder but the react added some weird character to the key and change my key.
I want to use react-grid-layout but the keys should be the same of the layout config props. so, I need to react stop changing the keys.
Here is my code:
class Main extends React.Component {
render() {
const { index, children } = this.props;
// Sample1: /.$
const childWithHolders1 = React.Children.map(children, child => (
<div key={"holder" + child.key}>
holder key: {"holder" + child.key}
</div>
));
const realReactKeys1 = childWithHolders1.map(divComp => divComp.key + "\n");
// Sample2: .$ (just pass number to the key)
const childWithHolders2 = React.Children.map(children, child => (
<div key={child.key}>
holder key: {child.key}
</div>
));
const realReactKeys2 = childWithHolders2.map(divComp => divComp.key + "\n");
return (
<div>
<h2>Sample 1</h2>
{childWithHolders1}
<br />
<h3>The Real React Keys: </h3>
<pre>{realReactKeys1}</pre>
<br /><h2>Sample 2</h2>
{childWithHolders2}
<br />
<h3>The Real React Keys: </h3>
<pre>{realReactKeys2}</pre>
</div>
);
}
}
class Child extends React.Component {
render() {
const { index } = this.props;
return (
<p>Child Index: {index}</p>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<h2>What is weird "/.$" or ".$" in the react key name ???</h2>
<Main>
<Child key="1" index="1" />
<Child key="2" index="1" />
<Child key="3" index="1" />
</Main>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I want to create a React HOC that would ideally receive two components instead of one wrapped component and toggle between them. That is, in the code below, instead of <h3>component one</h3> and <h3>component two<h3>, they would each represent child components. How would I be able to accomplish this? Some psuedo code for how I would write this HOC:
<HOC>
<ComponentOne />
<ComponentTwo />
</HOC>
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
hoc(componentOne, componentTwo)
class HOC extends React.Component {
constructor() {
super();
this.state = {
onClick: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({onClick: !this.state.onClick});
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
<h3>component one</h3> :
<h3>component two</h3>
}
</div>
);
}
}
ReactDOM.render(<HOC />, app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I am not sure if I understood you. Why do you need it to be HOC?
If you would pass components as props like that:
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
Then you would be able to access them using props.
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
this.props.componentOne :
this.props.componentTwo
}
</div>
);
}
If a component has more than one child then this.props.children will be an array.
class HOC extends React.Component {
// ... rest of code ....
render() {
const { onClick } = this.state;
const { children } = this.props;
return !onClick ? children[0] : children[1];
}
}
Then use it like so:
<HOC>
<div>Child One</div>
<div>Child Two</div>
</HOC>
Obviously this will only work with two children but you could extend it by passing an integer to <HOC> through props to tell it what child to select.
Edit
After a quick look at the docs this is a better version of what I wrote above as this.props.children is not an array, it is an opaque data structure:
class HOC extends React.Component {
// ... rest of code ...
render() {
const { onClick } = this.state;
const children = React.Children.toArray(this.props.children);
return !onClick ? children[0] : children[1];
}
}
Let's say I have a component named Match (renders span) and a string like Matched text to be wrapped with that component.
I'm expecting a final result like this:
<Match>Matched <Match>text</Match></Match>
On first wrap, my source text turns into a component like this:
<Match>Matched text</Match>
With this, is it possible wrap the inside of it (the text part) with another Match component? What should be the approach here?
Thanks.
What you need is ofcourse possible, each instance of the Match is different and whatever you write between Match tags are the children of the match component. So you just need
class Match extends React.Component {
render() {
return (
<span>{this.props.children}</span>
)
}
}
class App extends React.Component {
render() {
return (
<Match>Matched <Match>text</Match></Match>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="app"></div>
It should work just right. Is it what you are looking for?
class Match extends React.Component {
render() {
if (this.props.children === 'text') {
return (
<span style={{ backgroundColor: 'yellow' }}>{this.props.children}</span>
)
} else {
return (
<span style={{ color: 'blue' }}>{this.props.children}</span>
)
}
}
}
class Test extends React.Component {
matchString(string) {
/* you can work with the raw string here if you want */
return string.split(' ')
.map(word => <Match>{word}</Match>);
/* inside the map you can do whatever you want here, maybe conditional wrapping, like if(word === 'text') <Match>word</Match> else word ... */
}
render() {
return (
<div>
{this.matchString('Matched text')}
</div>
);
}
}
ReactDOM.render(<Test />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<body id="root">
</body>
I have a grid component as follow:
import React, { Component } from 'react';
import Action from './action.jsx';
class Grid extends Component {
constructor(props) {
super(props);
this.maxN = 110;
this.tempArray = [];
this.question;
}
getRandomN() {
var randomN = Math.floor((Math.random() * this.maxN) + 1);
if(this.tempArray.indexOf(randomN) === -1) {
this.tempArray.push(randomN);
}
else {
randomN = this.getRandomN();
}
return randomN;
}
getRandomQuestion() {
this.question = this.props.current.data.questions[this.getRandomN()];
return this.question;
}
render() {
this.getRandomQuestion();
return (
<section className="game">
<div className="grid">
<div className="row">
<div ref="n1"></div>
<div ref="n2"></div>
<div ref="n3"></div>
<div ref="n4"></div>
<div ref="n5"></div>
<div ref="n6"></div>
</div>
<div className="row">
<div ref="n7"></div>
<div ref="n8"></div>
<div ref="n9"></div>
<div ref="n10"></div>
<div ref="n11"></div>
<div ref="n12"></div>
</div>
<div className="row">
<div ref="n13"></div>
<div ref="n14"></div>
<div ref="n15"></div>
<div ref="n16"></div>
<div ref="n17"></div>
<div ref="n18"></div>
</div>
<div className="row">
<div ref="n19"></div>
<div ref="n20"></div>
<div ref="n21"></div>
<div ref="n22"></div>
<div ref="n23"></div>
<div ref="n24"></div>
</div>
</div>
<Action question={this.question} getRandomQuestion={this.getRandomQuestion.bind(this)}/>
</section>
);
}
}
export default Grid;
inside the "Action" component, based on the correct or wrong answer coming from "getNewQuestion" I need to access a random grid element from the grid component. (any random going from "n1" to "n24" as assigned to each ref attribute)
import React, { Component } from 'react';
class Action extends Component {
constructor(props) {
super(props);
this.state = {
question: props.question
}
}
getNewQuestion(e) {
console.log(this.state.question.correct_option);
let answerId = "option_" + this.state.question.correct_option;
if(e.target.getAttribute('data-question') == answerId) {
this.setState({
question: this.props.getRandomQuestion()
});
}
else {
console.log('wrong');
React.findDOMNode(this.refs.n1).classList.add('fdsdfsdfsdfsdfsfsdf');
}
}
render() {
let state = this.state;
return(
<div className="action">
<div className="action-question">
<h3>{state.question.question}</h3>
</div>
<div className="action-answers">
<p data-question="option_1" onClick={this.getNewQuestion.bind(this)}>{state.question.option_1}</p>
<p data-question="option_2" onClick={this.getNewQuestion.bind(this)}>{state.question.option_2}</p>
<p data-question="option_3" onClick={this.getNewQuestion.bind(this)}>{state.question.option_3}</p>
<p data-question="option_4" onClick={this.getNewQuestion.bind(this)}>{state.question.option_4}</p>
</div>
</div>
);
}
}
export default Action;
inside the "if" statment of the "getNewQuestion" I would like to do something like:
n2.classList.addClass('hidden');
I can't figure out how to access a parent's dom node from the "Action" component
Does the child really need to access the parent DOM directly? Shouldn't the parent Component know how to present itself? If so, then you can use callbacks that you pass down to the children, so that the children have the possibility to notify the parent when it should change.
const Child = ({modifyParent}) => (
<div onClick={ modifyParent } >Click me!</div>
);
const Parent = () => {
const modifyMyOwnStyle = event => {
// here you have easy access
// if you want to style the parent.
alert('Modifying style, based on child call');
}
return (
<Child modifyParent={ modifyMyOwnStyle }/>
);
};
ReactDOM.render(<Parent />, document.getElementById('root'));
Runnable JSFiddle demo here
You can get the ref of a component and pass this to its children like so:
render() {
return (
<div ref={node => this.node = node}>
<SomeChild parent={this.node} />
</div>
)
}
read more about it here: https://facebook.github.io/react/docs/refs-and-the-dom.html
However I have to say that doing this is usually a bad idea, and I would reconsider if you really need to pass the node, or if there is another way around the problem.
EDIT: As jonahe's comment shows you can usually get around the problem by passing a callback to the child component that you can fire when something needs to happen in the parent component
Better than accessing parent's DOM node directly, you can use a callback prop that does it for you.
Something like:
class Grid extends Component {
constructor(props) {
super(props)
this.accessRandomElement = this.accessRandomElement.bind(this)
}
accessRandomElement() {
// do you thing
}
render() {
this.getRandomQuestion()
return (
<section className="game">
...
<Action
question={this.question}
onAccessYourRandomElement={this.accessRandomElement}
///
/>
</section>
)
}
}
and then from inside Action you call this.props.onAccessYourRandomElement()
I'm new to React and I'm puzzled on something kind of basic.
I need to append a component to the DOM after the DOM is rendered, on a click event.
My initial attempt is as follows, and it doesn't work. But it's the best thing I've thought to try. (Apologies in advance for mixing jQuery with React.)
ParentComponent = class ParentComponent extends React.Component {
constructor () {
this.addChild = this.addChild.bind(this);
}
addChild (event) {
event.preventDefault();
$("#children-pane").append(<ChildComponent/>);
}
render () {
return (
<div className="card calculator">
<p><a href="#" onClick={this.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
<ChildComponent/>
</div>
</div>
);
}
};
Hopefully it's clear what I need to do, and I hope you can help me attain an appropriate solution.
Don't use jQuery to manipulate the DOM when you're using React. React components should render a representation of what they should look like given a certain state; what DOM that translates to is taken care of by React itself.
What you want to do is store the "state which determines what gets rendered" higher up the chain, and pass it down. If you are rendering n children, that state should be "owned" by whatever contains your component. eg:
class AppComponent extends React.Component {
state = {
numChildren: 0
}
render () {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<ChildComponent key={i} number={i} />);
};
return (
<ParentComponent addChild={this.onAddChild}>
{children}
</ParentComponent>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
}
const ParentComponent = props => (
<div className="card calculator">
<p><a href="#" onClick={props.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
{props.children}
</div>
</div>
);
const ChildComponent = props => <div>{"I am child " + props.number}</div>;
As #Alex McMillan mentioned, use state to dictate what should be rendered in the dom.
In the example below I have an input field and I want to add a second one when the user clicks the button, the onClick event handler calls handleAddSecondInput( ) which changes inputLinkClicked to true. I am using a ternary operator to check for the truthy state, which renders the second input field
class HealthConditions extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLinkClicked: false
}
}
handleAddSecondInput() {
this.setState({
inputLinkClicked: true
})
}
render() {
return(
<main id="wrapper" className="" data-reset-cookie-tab>
<div id="content" role="main">
<div className="inner-block">
<H1Heading title="Tell us about any disabilities, illnesses or ongoing conditions"/>
<InputField label="Name of condition"
InputType="text"
InputId="id-condition"
InputName="condition"
/>
{
this.state.inputLinkClicked?
<InputField label=""
InputType="text"
InputId="id-condition2"
InputName="condition2"
/>
:
<div></div>
}
<button
type="button"
className="make-button-link"
data-add-button=""
href="#"
onClick={this.handleAddSecondInput}
>
Add a condition
</button>
<FormButton buttonLabel="Next"
handleSubmit={this.handleSubmit}
linkto={
this.state.illnessOrDisability === 'true' ?
"/404"
:
"/add-your-details"
}
/>
<BackLink backLink="/add-your-details" />
</div>
</div>
</main>
);
}
}