Why does render not erase the state of children in Reactjs? - javascript

According to the react documentation,
when setState is called by the user, render is soon after called "behind the scenes". render usually returns a JSX element (though a couple other types are allowed). My question is, since render creates a new JSX element each time, and since setState called on a certain component then calls render on the same component, how is the old state of the components children retained?
I think this question is clear in and of itself,
but to illustrate exactly what i mean i will give a toy example.
export class App extends Component {
constructor (props) {
super(props);
this.state = {
foo: [null]
};
this.addToFoo = this.addToFoo.bind(this);
}
addToFoo() {
this.setState(state => {
return {foo: [...state.foo, null]};
});
}
render() {
return (<div onClick={this.addToFoo}>{this.state.foo.map( el => (<Bar></Bar>))}</div>);
}
}
with Bar defined as
export class Bar extends Component {
constructor(props) {
super(props);
this.state = {color: "#111111"};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
this.setState(state => {
if (state.color === "#111111")
return { color: "#dddddd"}
else
return { color: "#111111"};
})
}
render() {
return (
<div style={{color: this.state.color}} onClick={this.changeColor}>
Howdy!
</div>
);
}
}
Here, when the child is clicked, it toggles its color between a light gray and a dark gray.
When the parent is clicked it changes its state by adding an element to foo.
render is then called on parent behind the scenes, which returns one JSX element that contains foo.length child components of type Bar. However, somehow, the previous state of the children is retained (meaning the colors of the previously included children stay the same, and only the new one is default constructed), even though the foo.length components were newly returned and did not reference the old children at all. How is this possible?
Sandbox

React does lots of work to check if new render returns absolutely new component, or if it is the same component. Their diffing algorithm include comparisons and some heuristics for this, but you are not required to know in details how it works, since it can change internally. In your particular case this work may result in creating absolutely new component instead of reusing previous, because you don't use keys.
More in-depth info on diffing you can read in react documentation

render() is indeed called "behind the scenes" when there is a change to the state of the parent element. But it doesn't simply instantiate all the class components again and output the result of the render() call to the browser.
Instead, React maintains a virtual DOM, a copy of the DOM visible in the browser. It compares the output of the render() call to that virtual DOM, and it is only the changes that will be output to the real DOM.
As part of carrying out this comparison, for each component it will determine if it is the same component that was there in the previous render by comparing to the virtual DOM (exactly how it does this would require a close inspection of the algorithm).
If a class component is determined to be the same component as before, it will not instantiate it again but instead call the component's render() method using the same class instance and thus the same state as before. (As this documentation explains, a class component's constructor is only called when it is mounted.)
In this way children class components can retain state.

Related

React: Storing static variable in state vs render variable

I found a similar discussion to my question here but wanted to dig deeper.
I have yet to find any React documentation that discusses whether static variables (that are unchanging but need to be used for a React view) should be stored as a state variable or be rebuilt each time the component is rerendered.
Let's say that I have a React component that takes in a prop, which is a list, and I want to map this list to a certain output. In my use case I am turning the list into the format for the options my component's select picker will use. Once this list is made, it does not need to change. All options will remain the same for the rest of the component's use.
It feels weird to throw it into state, although it just seems more efficient. Let's go through two approaches.
Approach #1: Store option list in component state.
class Component extends React.Component {
constructor(props) {
this.state = {
options: props.list.map(some => computation)
}
}
render() {
return ( <SelectPicker options={this.state.options} /> )
}
}
Approach #2: Recreate variable upon each rerender.
class Component extends React.Component {
constructor(props) {
...
}
render() {
const options = this.props.list.map(some => computation)
return ( <SelectPicker options={options} /> )
}
}
The latter seems more correct, in that since the options array is never meant to change, it is only meant to be initialized once, and it doesn't make sense to put a watcher on it for being part of state. The former just seems more efficient, as we only compute this value once, rather than recomputing it every single time the component is rerendered. Yes React will compare the state between rerenders, but won't it compare the options list references? Whereas the latter example will completely rebuild a new list? Tbh, neither of these seem like the "clean" approaches to this.
The better option is neither. In your constructor function, set this.options to the mapped array and then access it with this.options in your render method.
class Component extends React.Component {
constructor(props) {
this.options = props.list.map(some => computation);
}
render() {
return ( <SelectPicker options={this.options} /> )
}
}
This avoids creating the same array over and over again. It's perfectly fine to set values on this. directly, the point of state is that if a state variable changes the render method is called again.

Why I am getting the old state values after the props change

I just want to understand why in the application I have following situation, below is my constructor of class component:
constructor(props) {
super(props);
this.state = {
tableAlerts: props.householdAlerts,
initialAlerts: props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}
in render function I have:
const { householdAlerts } = this.props;
My issue is that in constructor I got empty array, but in render funtion I have the data. Is it possible to get the data in constructor?
This is a very bad pattern when using the class component. You are ignoring any props updates when you copy the value into state. to manage it:
It requires you to manage two sources of data for the same variable: state and props. Thus, you need to add another render each time your prop change by setting it into state (don't forget to test on equality from prev and next values to avoid being in an infinite loop).
You can avoid setting the state each time your props change by using the getderivedstatefromprops lifecycle method.
So the recommendation is: just use the props; do not copy props into state.
To learn more why you shouldn't, I highly recommend this article.
It is not recommended to set your initial component state in the constructor like so because you gonna lose the ability to use { setState } method after to update this property/state.
The best practice is indeed to refer directly to the prop with { this.prop.householdAlerts }, and keep the state usage for local (or in child components} cases.
if anyhow you want to store props in component state for some reason, call it in lifeCycle -
componentDidMount() {
const { tableAlerts, initialAlerts } = this.props;
this.setState({ tableAlerts, initialAlerts });
}
Hagai Harari is right. Nevertheless, your actual problem seems to be that during your initial rendering the array is empty. Can you ensure that the array has some items, when your component is rendered for the first time?
First rendering -> calls constructor
<YourComponent householdAlerts={[]} />
Second rendering -> updates component
<YourComponent householdAlerts={[alert1, alert2, alert3]} />
If you want initial state to have the prop value.Try something like this with 'this' keyword
constructor(props) {
super(props);
this.state = {
tableAlerts: this.props.householdAlerts,
initialAlerts: this.props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}

Is there a problem with setting React.Component state equal to a this.props value in typescript?

I am attempting to assign an object that is passed into a React.Component as a property to a state value like so,
state = {
rota: this.props.rota
}
render() {
// const { cleaning, chairs, creche, flowers } = this.state.rota;
const { cleaning, chairs, creche, flowers } = this.props.rota;
console.log(creche);
}
The commented out section where it get's the value of the state prints out an empty string, however the property values are correct. Am I doing something wrong when assigning the props.rota to the state.rota?
I am using typescript and have done exactly this in another place in my program - however the property being passed in was a value type (string) rather than an object type.
Thanks!
That's usually a bad practice.
Take your case.
You get a value as prop from a parent component.
The inner component will already be re rendered every time that this value changes in the parent component.
If you go with an approach like yours, what most probably you will do, is to change that value inside the inner component too (through state), whose changes will not be reflected on the parent component.
You are actually breaking the design pattern of uni directional data flow, on which react relies a lot on.
So my personal opinion is to lift up the state in this case and avoid such kind of situations. Use callbacks instead if you want to communicate changes to the parent, or use some state management (context, redux, etc..).
Or design a better solution using HOC or render props Components.
Even though #quirimmo has pretty much answered your question, if you want to do this sometime in the future, the easiest way would be to use a constructor function and pass the props in as a param, and then just set that as the default value of the state
class SomeComponent extends Component {
constructor(props){
super(props);
this.state = {
rota: props.rota,
}
}
}
This makes sure that the prop is actually available in the moment you want to set the initial state, since the constructor is the first function that is called in the component lifecycle.

React: How to check the type of a child in the parent's render function?

I know that during render I can get the child through refs and, for example, call a function on the child (which I can add to the child for this purpose) to determine the type of the child.
<Child ref={(child) => {this._childType = child.myFunctionToGetTheType();}} />
But in this example the function isn't actually called until the Child is mounted, so after the render of the Parent has finished executing.
I have a parent component that receives its children through props. Because of React limitations I need to treat a specific child in a special way BEFORE the render of the parent has finished executing (i.e. return something else from the parent's render function for that specific child).
Is it possible to determine the type of the child before returning from the parent's render function (i.e. without using refs)?
I've had the same issue, where I was relying on child.type.name to determine the type of component. While this works fine for me, the issue is that older browsers somehow do not support that so I had to find another way. I was using Stateless Functional Components and did not want to switch away, so ended up exploiting props
const MySFC = () => {
//...
return (
<div className="MySFC"></div>
);
};
MySFC.propTypes = {
type: PropTypes.string
};
MySFC.defaultProps = {
type: "MySFC"
}
export default MySFC;
then instead of using child.type.name === 'MySFC' I used child.props.type === 'MySFC'
not ideal, but works
I have added a static function to the class that extends React.Element and it seems that I am able to access it through child.type.theStaticFunction. That probably still isn't using the React API correctly but at least it works after the code has been minified (child.type.name didn't work because the minifier was replacing class names with shorter versions).
export default class MyType extends React.Component {
static isMyType() {
return true;
}
}
then when processing the children in render
static _isChildOfMyType(child) {
const isMyType = child.type && child.type.isMyType && elem.type.isMyType();
return !!isMyType;
}

React: update one item in a list without recreating all items

Let's say I have a list of 1000 items. And I rendering it with React, like this:
class Parent extends React.Component {
render() {
// this.state.list is a list of 1000 items
return <List list={this.state.list} />;
}
}
class List extends React.Component {
render() {
// here we're looping through this.props.list and creating 1000 new Items
var list = this.props.list.map(item => {
return <Item key={item.key} item={item} />;
});
return <div>{list}</div>;
}
}
class Item extends React.Component {
shouldComponentUpdate() {
// here I comparing old state/props with new
}
render() {
// some rendering here...
}
}
With a relatively long list map() takes about 10-20ms and I can notice a small lag in the interface.
Can I prevent recreation of 1000 React objects every time when I only need to update one?
You can do it by using any state management library, so that your Parent doesn't keep track of this.state.list => your List only re-renders when new Item is added. And the individual Item will re-render when they are updated.
Lets say you use redux.
Your code will become something like this:
// Parent.js
class Parent extends React.Component {
render() {
return <List />;
}
}
// List.js
class List extends React.Component {
render() {
var list = this.props.list.map(item => {
return <Item key={item.key} uniqueKey={item.key} />;
});
return <div>{list}</div>;
}
}
const mapStateToProps = (state) => ({
list: getList(state)
});
export default connect(mapStateToProps)(List);
// Item.js
class Item extends React.Component {
shouldComponentUpdate() {
}
render() {
}
}
const mapStateToProps = (state, ownProps) => ({
item: getItemByKey(ownProps.uniqueKey)
});
export default connect(mapStateToProps)(Item);
Of course, you have to implement the reducer and the two selectors getList and getItemByKey.
With this, you List re-render will be trigger if new elements added, or if you change item.key (which you shouldn't)
EDIT:
My inital suggestions only addressed possible efficiency improvements to rendered
lists and did not address the question about limiting the re-rendering
of components as a result of the list changing.
See #xiaofan2406's answer for a clean solution to the original question.
Libraries that help make rendering long lists more efficient and easy:
React Infinite
React-Virtualized
When you change your data, react default operation is to render all children components, and creat virtual dom to judge which component is need to be rerender.
So, if we can let react know there is only one component need to be rerender. It can save times.
You can use shouldComponentsUpdate in your list component.
If in this function return false, react will not create vitual dom to judge.
I assume your data like this [{name: 'name_1'}, {name: 'name_2'}]
class Item extends React.Component {
// you need judge if props and state have been changed, if not
// execute return false;
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.name === this.props.name) return false;
return true;
}
render() {
return (
<li>{this.props.name}</li>
)
}
}
As react just render what have been changed component. So if you just change one item's data, others will not do render.
There are a few things you can do:
When you build, make sure you are setting NODE_ENV to production. e.g. NODE_ENV=production npm run build or similar. ReactJS performs a lot of safety checks when NODE_ENV is not set to production, such as PropType checks. Switching these off should give you a >2x performance improvement for React rendering, and is vital for your production build (though leave it off during development - those safety checks help prevent bugs!). You may find this is good enough for the number of items you need to support.
If the elements are in a scrollable panel, and you can only see a few of them, you can set things up only to render the visible subset of elements. This is easiest when the items have fixed height. The basic approach is to add firstRendered/lastRendered props to your List state (that's first inclusive and last exclusive of course). In List.render, render a filler blank div (or tr if applicable) of the correct height (firstRendered * itemHeight), then your rendered range of items [firstRendered, lastRendered), then another filler div with the remaining height ((totalItems - lastRendered) * itemHeight). Make sure you give your fillers and items fixed keys. You then just need to handle onScroll on the scrollable div, and work out what the correct range to render is (generally you want to render a decent overlap off the top and bottom, also you want to only trigger a setState to change the range when you get near to the edge of it). A crazier alternative is to render and implement your own scrollbar (which is what Facebook's own FixedDataTable does I think - https://facebook.github.io/fixed-data-table/). There are lots of examples of this general approach here https://react.rocks/tag/InfiniteScroll
Use a sideways loading approach using a state management library. For larger apps this is essential anyway. Rather than passing the Items' state down from the top, have each Item retrieve its own state, either from 'global' state (as in classical Flux), or via React context (as in modern Flux implementations, MobX, etc.). That way, when an item changes, only that item needs to re-render.
One way to avoid looping through the component list every render would be to do it outside of render function and save it to a variable.
class Item extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item;
}
render() {
return <li>{this.props.item}</li>;
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.items = [];
this.update = this.update.bind(this);
}
componentWillMount() {
this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
}
update(index) {
this.items[index] = <Item key={index} item={'item changed'} />
this.forceUpdate();
}
render() {
return <div>
<button onClick={() => { this.update(199); }}>Update</button>
<ul>{this.items}</ul>
</div>
}
}
There is a way to do this, but I don't think the React team would call it Kosher so to speak. Basically, instead of the parent having state, it has lists of refs. Your components have their own state. Your components are created once in the parent and stored in a parent's ref property which holds an array of all those components. So the components are never recreated on each rerender, and instead are persisted. You also would need a list of refs that attach to a function in each component to allow the parent to call individual components (in hooks you can use imperativehandle to do this).
Now, when the user does something that would cause the data to change for a specific component in that list, you would find that component's ref in the list of attached functions and call the function on it. The component could then update and rerender itself based off that function call from its parent, without other components being affected/recreated/rerendered.
I believe this is called imperative programming rather than declarative, and React doesn't like it. I personally have used this technique in my own projects for similar reasons to you, and it worked for me.

Categories

Resources