React: modifying children
Anyone who knows me knows I hate layering on complexity made by someone else. So if I use React, I like to avoid adding yet more frameworks, modules, and libraries to it to make it do "more".
So when I was idly trying out making a monopoly game in React, I had a desire to make a form that could track the state of variables (and later incorporate error-checking code I've written before) without having to resort to Redux Form or Context or any number of solutions. It just feels superfluous like it is subverting the purpose of the original framework.
Don't get me wrong, I understand there is a point where it makes sense to use them in many circumstances. I just want to push myself to do it if I am doing something as a learning product. Using a ton of npm modules while doing a pleasure learning exercise is like going running for exercise and taking an uber halfway.
So I was trying to make my form and it was quickly turning into a rabbit hole. Everything was fine at first. I wanted to use "children" because it would be simpler to drop a pre-existing form element into the text-area of a JSX element than adding a whole object as properties.
//doing this
<Form>
<input type="text" placeholder="enter name"/>
<input type="checkbox" value="yes">
</Form>
//versus this
<Form contents={
[
<input type="text" placeholder="enter name"/>,
<input type="checkbox" value="yes">
]
} />
Very quickly I found I wanted to do more. Obviously I have to track changes to the inputs. You don't need to directly modify the inputs themselves, because events bubble. So you COULD track changes in the parent element.
//I don't like doing this, but it is quick to display binding this way for the example
handleChange = (event) =>{
const nameOfElement = event.target.getAttribute('name');
const valueOfElement = event.target.value;
}
render(){
<div onChange ={ this.handleChange }>
{ this.props.children }
</div>
}
That works, but for other things, you need more. Like if you want to be able to change the value of the input, you need to be able to directly modify the input. You could go through the input via JS and modify it, but that would be ridiculous. You shouldn't modify the element directly. Worst case scenario you would add a reference in React to the element, but you can't do that without modifying the child.
React doesn't make it easy to modify an element. You CAN see things like props and such in the incoming element. So I made an componentDidMount and went to work.
componentDidMount(){
const children = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
const newChildren = children.map( component =>
React.cloneElement( component, {
onChange: this.updateValue,
value: this.state[ component.props.name ]
})
);
this.setState({
children: newChildren
});
}
The part at the top is interesting. For some reason, React will give you an array in this.props.children if there was more than 1 child, and it will give you a single object representing the dom element/component if there is only 1 child. Why they didn't put it into an array (an array of 1 element or many) is beyond me. If you happen to know, post in the comments below!
However, that doesn't really work. Your inputs won't update correctly because the children themselves won't update. You could make a subcomponent object, but then we're just getting crazy. So we instead need to basically render it over and over as a clone.
So instead we move our cloneElement into a new method so it can be called every render.
renderChildren(){
const children = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
const newChildren = children.map( component =>
React.cloneElement( component, {
onChange: this.updateValue,
value: this.state[ component.props.name ]
})
);
return newChildren;
}
render(){
return (
<div>
{ this.renderChildren() }
<button onClick={this.submitForm}>{this.props.callbackLabel}</button></div>
)
}
Which really seemed excessive. I talked with some colleagues here at LearningFuze, and Scott, grand pubbah of React, scratched his head and says "yeah, that seems like the way". He did say something very sage, though: "That's pretty much what it does with elements. It rerenders them, or at least appears to". That's paraphrased, but it encompasses what seems to be true. React basically tears down the dom and rebuilds it over and over again. It does it in a way that obviously isn't that insane, as it works fairly well.
So that's about it. There may be better ways, but if there is one lesson to learn here: MAKE STUFF. You can't get into corners if you don't move. You can't figure out solutions if you don't make problems. So make new code every week to try things. You'll be amazed at the rabbit holes you fall down. Sometimes you will discover things that are noteworthy.