Unnecessary DOM update for dynamic React elements - reactjs

I have a React project that generates some DOM elements "dynamically" within JSX:
<div className='ui form'>
<h2 className="header">{subtype}</h2>
{
subtypes[subtype].fields.map((field) =>
<div className='field' key={field.name}>
<label>{field.label}</label>
<input name={field.name}
value={entity[field.name]}
onChange={onInputChange}/>
</div>
)
}
</div>
For a specific component, the generated input fields don't ever change during the life of the application (only their props change), so it is just a way to generate forms that are actually static.
So it is exactly equivalent to this "static" JSX:
<div className='ui form'>
<h2 className="header">{subtype}</h2>
<div className='field' key='field1'>
<label>Field 1</label>
<input name='field1'
value={entity['field1']}
onChange={onInputChange}/>
</div>
<div className='field' key='field2'>
<label>Field 2</label>
<input name='field2'
value={entity['field2']}
onChange={onInputChange}/>
</div>
</div>
If I used the first code snippet, then the HTML DOM elements get recreated on every change to state / props. If I use the second snippet, then the HTML appears to be unchanged and only the field values are updated (React can detect in the second instance that the virtual DOM elements are still the same, but not in the first instance)
Is there a way for me to create the "dynamic" virtual DOM in the first code example in a way that it can be cached and reused so that React sees it as being the same on each render?
Many thanks

Where is subtypes coming from? From what I understand you are receiving this in the component's props. If that is the case, you need to store this variable in this component's state. Then, you need to update it's state in it's componentWillReceiveProps lifecycle function.
The thing is, your component will only re-render when it's setState function is called. Hence, the components will not re-render when it's props change (after it has already been mounted).
class SimpleCom extends React.Component {
constructor(props) {
super(props);
this.state = {
subtypes: props.subtypes
}
}
componentWillReceiveProps(props) {
this.setState({
subtypes: props.subtypes
});
}
render() {
const subtypes = this.state.subtypes;
return (
<div className='ui form'>
<h2 className="header">{subtype}</h2>
{
subtypes[subtype].fields.map((field) =>
<div className='field' key={field.name}>
<label>{field.label}</label>
<input name={field.name}
value={entity[field.name]}
onChange={onInputChange}/>
</div>
)
}
</div>
);
}
}

Related

Avoid component update while changing state vaiable

Background
I have a React component which includes further two more components. I component includes a chart(build with react-charts) and the other is a simple input field. I initially make not visible but it become visible when someone clicks icon over there.
Issue, child rerenders when state changes
Now the problem is whenever I toggle this input field it automatically refreshes my graph. In fact when I type into my input field it also refreshes the graph. I think it rerenderes the graph as I update the state variable. I want to stop this behavior. Any suggestions on how can I do this.
Component Screenshot(https://i.imgur.com/zeCQ6FC.png)
Component Code
<div className="row">
<DealGraph ref={this.dealRef} />
<div className="col-md-4">
<div className="row">
<div style={style} className="col-md-12 bg-white border-radius-10 default-shadow">
<h3 className="sub-heading roboto" style={border}>
Create Deals
</h3>
<input
type="text"
name="deal"
className="form-control mgt-30"
value="Deal One"
readOnly
/>
<button
type="button"
onClick={this.showAddBlock}
style={button}
className="golden-button create-deal-button"
>
<i className="fa fa-plus"></i>
</button>
{addDealStatus ? (
<div className="col-md-12 add-deal-box pd-0-0">
<input
type="text"
className="form-control mgt-30 mgb-10"
name="add-deals"
placeholder="Add Deals"
/>
<button type="button" className="golden-button flex all-center">
Add
</button>
</div>
) : null}
</div>
</div>
</div>
</div>
Toggle function
showAddBlock=() => {
this.setState({addDealStatus:!this.state.addDealStatus})
}
use PureComponent
To stop a child component rerendering from it parent you should make the child a pure component.
import React from 'react';
class DealGraph extends React.PureComponent { // notice PureComponent
render() {
const { label, score = 0, total = Math.max(1, score) } = this.props;
return (
<div>
/* Your graph code */
</div>
)
}
}
Use the { pure } HOC from Recompose
You can use a functional component that is wrapped in the pure HOC from recompose
import React from 'react';
import { pure } from 'recompose';
function DealGraph(props) {
return (
<div>
/* Your graph code */
</div>
)
}
export default pure(DealGraph); // notice pure HOC
A quick solution would be to move the input to an own component.
A more sophisticated solution is adapting the shouldComponentUpdate function in your DealGraphcomponent like stated here React: Parent component re-renders all children, even those that haven't changed on state change
By Default while rendering every component react checks for shouldComponentUpdate .React Components dont implement shouldComponentUpdate by default.So either we can implement a shouldComponentUpdate. Or Make the child class as a pure component.

component is unmounted and constructor is called when rendering it in a different place in the DOM

I am trying to implement a minimize/maximize feature in React. I have a div that serves as an information panel and by clicking on a button I would like it to toggle between maximized / minimized states. My top level app component has a boolean state field (maximizedInfo in the example below) that tracks whether the information panel is maximized or not and accordingly renders either just the panel or the full grid of my application with many other DOM elements. The below code is obviously a minified example but the main idea is that the render() method of my top-level component generates two very different DOM trees depending on the state. Unfortunately, I have discovered that my information panel component keeps getting unmounted and the constructor is called on every state change, thus losing the state the information panel component had accumulated.
What is the proper way to address that and implement this sort of functionality in React?
const React = require('react');
class InformationPanel extends React.Component {
constructor(props) {
console.log('InformationPanel:: constructor'); // this keeps getting called
super(props);
}
render() {
return (
<div>
<a id='information' class='nav-link' href="#" onClick={this.props.toggleInfoPanel}>toggle</a>
short info
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
}
1st of all - keep structure/tree unmodified [and more maintainable]:
render() {
return (
<div class='container-fluid'>
<div class='row no-gutters'>
{this.state.maximizedInfo &&
<div class='col-10'>
some other info that takes up more space ...
</div>
}
<div key="infoPanel" class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
Adding key prop helps in reconcilation - article
After this change <InformationPanel/> should not be rerendered. Notice that change is on parent - the place where child nodes differs. Parent not changed, props not changed, no rerendering.
2nd - above is not enough - we want size change!
I'd say that's a 'structural problem' - styling should be done inside <InformationPanel/> with required change passed as prop (f.e.):
<InformationPanel key="infoPanel" wide={!this.state.maximizedInfo} toggleInfoPanel={this.toggleInfoPanel}/>
// and in render in <InformationPanel/>
<div className={`padding-0 ${this.props.wide ? 'col-12' : 'col-2'}`}>
...
Still use key prop!
Other options for conditional styling in this thread
xadm's answer was correct that key is essential for tree reconciliation. The thing is, I discovered that the key needs to be present in the parent components, not necessarily in the InformationPanel component. The below code works:
if (this.state.maximizedInfo)
return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div key='c' class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div key='c' class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
Since you don't want to lose state in InformationPanel, you can declare it outside conditional rendering so that it won't be getting unmounted on state change. Code will look something like below:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
const informationPanel = <InformationPanel toggleInfoPanel={this.toggleInfoPanel} />
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
}
}

Value not updating inside a ReactJS function

I have a ReactJs function that displays a simple dialogue box, and is intended to update a value for the parent component. The function looks like this:
export function MyDialogue(props: IMyProps) {
var myValue = 0;
return (
<div style={style}>
<label>Enter a number here: </label>
<input type="text" value={myValue} />
<button onClick={() => props.updateFunc(myValue)}>Update</button>
</div>
);
}
I've tried several variations of this; for example, passing in props.myValue, and even changing the function to a class and passing this.state.myValue in. In all but this example, myValue remains as it was in the parent class, and in this version, always 0.
updateFunc simply calls setState on the parent class, and having traced through it, it never gets called with the changed value.
I've seen some documentation that says to essentially handle the onChange event - is this the only way to make this work, or is there a way to implement data binding in React?
Just bind back your input to the parent's state via props.value;
MyDialogue.js
export function MyDialogue(props: IMyProps) {
return (
<div style={style}>
...
<input type="text" value={props.value} />
...
</div>
);
}
Parent.js
....
render(){
const { dialogueValue } = this.state;
return <MyDialuge value={dialogueValue} updateFunc={this.handleUpdate} />
}
You are using uncontrolled input because You are not keeping value of input inside state.
Solution is
With uncontrolled:
export class MyDialogue extends React.Component<IMyProps, {}>{
constructor() {
super();
this.input = React.createRef();
}
return (
<div style={style}>
<label>Enter a number here: </label>
<input type="text" ref={this.input} />
<button onClick={() => props.updateFunc(this.input.current.value)}>Update</button>
</div>
);
}
With controlled:
Maintain myValue state in parent and pass it to child.
and on change on input event call a function of parent which change myValue using setState,
Maintain myValye state inside MyDialogue and onClick pass it to parent.
You need to change this component to a stateful component
then do a two-way binding for your textbox and have it talk to local state and then use that state value to update parent component.
export class MyDialogue extends React.Component<IMyProps, {}>{
constructor() {
super();
this.state = {
myValue: 0
}
}
onChangeHandler = (event:any) =>{
this.setState({myValue:event.target.value});
}
return (
<div style={style}>
<label>Enter a number here: </label>
<input type="text" value={this.state.myValue} onChange={this.onChangeHandler}/>
<button onClick={() => props.updateFunc(this.state.myValue)}>Update</button>
</div>
);
}

React access Dom Nodes from this.props.children

Let's say I have a Card that contains a login Form
<Card>
<LoginForm/>
</Card>
How do I access the nodes from the Form within the Card render function?
<Form >
<input type="text" name="email"/>
<input type="password" name="password"/>
<input type="submit"/>
</Form>
Because what i´d like to do is to render the submitbutton not within the props.children context but render it wrapped outside of the given child!
render () {
return (
<div className="card">
<div className="inner">
{/* render Children */}
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
{/* render submit from login form here, not above */
</div>)
There are some components which actually do what I want. For example the Tabs component from react-toolbox. They somehow manage to render what's within the Tab (children) somewhere else
Just for instance
<Tabs index={this.state.inverseIndex} onChange={this.handleInverseTabChange} inverse>
<Tab label='First'><small>First Content</small></Tab>
<Tab label='Second'><small>Second Content</small></Tab>
<Tab label='Third'><small>Third Content</small></Tab>
<Tab label='Disabled' disabled><small>Disabled Content</small></Tab>
</Tabs>
Which will lead to the following html
As you can see the children from the tab where rendered within their own section
I do not want to change anything on the Form to solve this problem, I would like to pass the Form into the Card and let the Card decide how the Form will be rendered within the card render function.
Since I'm trying to implement the Google Material Design Card component and just use it as a template there are more elements coming which will need to be split up and placed at the positions I want them to be. The thing is I could actually place the relevant HTML around the Form to get it as the Card I want it to be, but then I wouldn't need the component at all.
There are some decent answers here, but none of them directly answer your question. Therefore, even though you should refactor your code (as elucidated below), I am going to provide you a working solution:
class Card extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
console.log(typeof this.props.children)
return (
<div>
{typeof this.props.children === 'object'
? React.cloneElement(this.props.children, { ref: (n) => this.form = n })
: null}
<button onClick={(e) => console.log(this.form.data)}>submit</button>
</div>
);
}
}
class Form extends React.Component {
constructor() {
super();
this.onChange = this.onChange.bind(this);
this.state = {};
}
onChange(e) {
this.data = e.target.value;
}
render() {
return (
<form>
<input type="text" onChange={this.onChange} />
</form>
);
}
}
ReactDOM.render(
<Card><Form /></Card>,
document.getElementById('container')
);
https://jsbin.com/fohehogozo/edit?js,console,output
By setting a property on the instance, you can then access that property from children by using a ref. I checked for typeof === object here, because there was only one child.
WARNING: this code is NOT PRODUCTION READY. Do not ever run this in production. The code I have demonstrated is a terrible hack, and you should never try this at home.
If you are trying to submit a form, maybe look at passing down an onChange event and storing the value (based on the name of the field) in the state of the Card. Then attach the onChange event on the inputs so as soon as they're updated, the data will be passed back up to the container for you to submit.
If you would like to split up the childrens passed, you can simply filter the children array to split up the children, however your childrens seem to be nested.
Why dont you let the cards children handle the separation between your inner container and other content?
I think restructuring in this case is more suitable than modifying the passed children property.
Also, pulling the submit button out of the actual form tags, would break your form as it would no longer submit without some custom connection between the button and the actual form.
Don't try to manipulate the DOM; it's generally an anti-pattern in React (though there are a few valid use cases). In your case, rather than literally trying to move the elements, I'd simply hide the button in the form and add it to the parent.
Assuming you have access to the internals of <LoginForm>, you can add a prop to hide the button:
const button =
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>;
<Form>
<input type="text" name="email"/>
<input type="password" name="password"/>
{!this.props.hideButton && button}
</Form>
Add the button to the Card component:
render() {
return (
<div className="card">
<div className="inner">
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>
</div>
);
}
Finally, in your parent:
<Card>
<LoginForm hideButton />
</Card>
All that said, it really feels like you need to structure your code better and break some of these components up into smaller, more reusable pieces. For example, the Card component probably shouldn't be affecting the button's style or conditionally rendering children; it should just add a frame around any children. Then you can create a more complex component that composes these simpler sub-components to to whatever you need.

React : Is this possible to replace a "placeholder template tag" send as children?

I have for example this code below :
<AjaxForm>
<input type="hidden" name="xxx" value="xxx" />
<div className="grid">
<div className="gdcol-xs-11">
[[SUBMIT_BUTTON]]
</div>
<div className="gdcol-xs-11">
[[CANCEL_BUTTON]]
</div>
</div>
</AjaxForm>
And I would like, for example, be able in the AjaxForm component to replace the tag placeholder 'SUBMIT_BUTTON' by this :
<a href="javascript:void(0);" onClick={this.handleSubmit}>VALIDATE</a>
Is there a way to do this by iterating on this.props.children in the AjaxForm component ?
Is this possible to find some text pattern by crawling all the children ?
Should I have to use refs or a key ?
Thank you in advance !
---- EDIT
To add some informations, this is the render of the AjaxForm Component
return (
<form action="" method="post" ref={this.ajaxRef} id={this.props.id} onSubmit={this.onSubmit}>
<input type="hidden" name="form_id" value={this.props.id} />
{this.props.children}
<input type="submit" value="" className="fake-submit" />
<div id={("ajax-") + this.props.id + ("-messages-container")} className="ajax-form-messages"></div>
</form>
)
I think I understand your issue now. You're generating a component inside AjaxForm and you want to be able to place that component dynamically. In that case you should create another component called AjaxFormContents (or whatever your specific form should be called) which receives your generated component via props and places it wherever you want.
// AjaxForm.js
...
render() {
return React.cloneElement(this.props.children, {
submitButton: this.generateSubmitButton() // or however you're doing it
})
}
...
Now whatever component you put as a child will have access to this component.
// AjaxFormContents.js
...
render() {
return ( // arrange your form contents however you like!
<div>
<input />
{ this.props.submitButton }
</div>
)
}
Then in your parent component:
// Parent.js
...
render() {
return (
<AjaxForm>
<AjaxFormContents />
</AjaxForm>
)
}
This should work, however another approach -- using a higher order component (HOC) would be a nice solution here as well, because your AjaxForm doesn't display anything, it just wraps your form contents.. instead AjaxForm can be an HOC which passes a generated submit button component to the wrapped component.
var ajaxForm = function (WrappedComponent) {
return React.createClass({
generateSubmitButton() {
return <a>your special submit button </a>
},
render() {
<WrappedComponent submitButton={ this.generateSubmitButton() } />
}
})
}
Then you can have the exact same AjaxFormContents component as above and when you export it:
// or module.exports, whichever one you're using.
export default ajaxForm(AjaxFormContents)

Resources