We have created a re-usable list component using ReactJS. However, due to performance issues, we decided to implement - shouldComponentUpdate method with the conditions on when should my list component render
public shouldComponentUpdate(nextProps: TreeItemInternalProps, nextState: {}): boolean {
if (this.props.treeObject.selected !== nextProps.treeObject.selected) {
return true;
}
if (this.props.treeObject.expanded !== nextProps.treeObject.expanded) {
return true;
}
return false;
}
Meaning, I wanted to render my component only when the value of the checkbox of list item changes.
Let's say, due to some reason, I can't do this anymore. Now, my alternatives are using PureComponent which does a shallow compare. So, it should render only the list item which changed. However, even using after using the PureComponent, the entire component is getting rendered.
To explain the scenario properly, see the following screenshots -
Without using PureComponent / using the conditional check in shouldComponentUpdate
Here, you can see the log "component is rendered" was called only once, meaning, only for the change I did in the prop (this is the same requirement, I want to implement using PureComponent)
With using PureComponent
Here, you can see the log "component is rendered" was called 3 times, even though I had changed just the props of first list item. This is using PureComponent
PureComponent is always rendering for you because most likely your Props or State contain ANY objects or arrays that are being created in the parent component during the parent's re renders.
As others of said, it does a shallow comparison for re-renders (meaning an object reference won't equal a NEW object reference, even if they are DEEP equal), so the parent component is acting as a bottleneck to it's PureComponent child, as every re-render recreates a whole new object reference
If you REALLY wanted to use a PureComponent, you would have to simplify all the other props in your component to be primitives or objects/arrays that do not get a new reference on every re render of the parent
Example of problem:
class Todo extends PureComponent {
render() {
return <div>this.props.foo</div>;
}
}
class MyTodoList extends Component {
render () {
const fooBar = {foo: 'bar'}
return <Todo fooBar={fooBar} />
}
}
Example of fix:
class Todo extends PureComponent {
render() {
return <div>this.props.foo</div>;
}
}
class MyTodoList extends Component {
render () {
return <Todo fooBar={props.fooBar} />
}
}
Probably the best thing you want to do is bring up that object creation as high as you can to a component that does NOT re render on these kind of changes, or simplify every single prop to a non-object.
React.PureComponent does the shallow comparison on their props and states.
So changing the object property of the state or props won't re-render the component.
We need to reconstruct the state or props only when the state or props in interests is changed.
For example, we can write the code as below to re-render only when forceRender flag is set.
class ShalloWCompareComponent extends React.PureComponent {
constructor() {
super();
this.state = {
userArray: [1, 2, 3, 4, 5]
}
}
update() {
const { forceRender } = this.props;
const { userArray } = this.state;
if (forceRender) {
this.setState({
userArray: [...userArray, 6]
});
} else {
this.setState({
userArray: userArray.push(6)
});
}
}
render() {
return <b>Array Length is: {this.state.userArray.length}</b>
}
}
If this component receives the props of forceRender={false}, calling update() wouldn't re-render the component thanks to the shallow comparison of the React.PureComponent.
Related
I am trying to store my child components in the parent constructor as attributes of the class instance. On the state change, I am expecting the parent (hence the child) to rerender and for the letter "b" to be on the screen verses "a". Why does this not work?
Does not show "b" on state change
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {letter : "a"};
setTimeout(() => this.setState({letter: "b"}, 1000);
}
this.child= <Child letter = this.state.letter>
}
render() {
return this.child;
}
}
class Child extends React.Component {
render() {
<p>{this.prop.letter}</p>
}
}
But when I do not store the child component as a class attribute of the parent, but instead render it directly in the render method, it works. The child component reflects the change. Is this because render creates a new JSX object every time it is run, and by storing the child jsx as a attribute of the parent object, I am essentially rendering that old child object?
Correctly Shows "b" on state change
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {letter : "a"};
setTimeout(() => this.setState({letter: "b"}, 1000);
}
}
render() {
return <Child letter = {this.state.letter}/>;
}
}
class Child extends React.Component {
render() {
<p>{this.prop.letter}</p>
}
}```
You can't return an object as render return statement, it should be most of the time a jsx so if you want to do your wrong way, you need something like this:
render() {
return <div>{this.child}</div>
}
But let's see why that's wrong, in React change in state or props will result in a rerender, and that's why you need to keep data in those places, otherwise like your case (if you change it like I showed above which is wrong), you get stale data and as you said the letter you're getting in the Child Component is old version and will not be updated
so let's say it in another way, the constructor function will only be called at the initialization and in that moment you set this.child attribute with the letter a, after 1000ms the state will change to letter b but you're constructor function will not be called again. so you still have the old value.
in the mean time because the state has changed from a to b render function will be invoked again, and here if you had the Child component in the render function as a jsx you get new data
I am trying to create a component that shouldn't when a certain property is true, but should perform a shallow compare (the default for PureComponent).
I've tried doing the following behavior:
export default class ContentsListView extends PureComponent<Props> {
shouldComponentUpdate(props: Props) {
if (props.selecting) {
return false;
}
return super.shouldComponentUpdate(props);
}
render() {
}
}
However, super.shouldComponentUpdate is not defined. Is there some way to "tap into" the shallow compare of PureComponent without writing my own?
You should not implement shouldComponentUpdate when you are extending your component using PureComponent. If however you want to use the shouldComponentUpdate functionality you can simply write a wrapper component around your original component or use HOC to implement your custom shouldComponentUpdate and render the PureComponent
Sample code
class ContentsListView extends PureComponent {
render() {
console.log("render list");
return (
...
);
}
}
export default class Wrapper extends React.Component {
shouldComponentUpdate(props) {
if (props.selecting) {
return false;
}
return true;
}
render() {
return <ContentsListView {...this.props} />;
}
}
You can see a working demo on codesandbox here
There is no super.shouldComponentUpdate in PureComponent because it implements shallow checks by checking isPureReactComponent property, not with shouldComponentUpdate. A warning is issued when both isPureReactComponent and shouldComponentUpdate are in use because shouldComponentUpdate efficiently overrides the behaviour of isPureReactComponent.
React doesn't expose its shallowEqual implementation, third-party implementation should be used.
In case this becomes a common task, own PureComponent implementation can be used for extension:
import shallowequal from 'shallowequal';
class MyPureComponent extends Component {
shouldComponentUpdate(props, state) {
if (arguments.length < 2)
throw new Error('Do not mess super arguments up');
return !shallowequal(props, this.props) || !shallowequal(state, this.state);
}
}
class Foo extends MyPureComponent {
shouldComponentUpdate(props, state) {
if (props.selecting) {
return false;
}
return super.shouldComponentUpdate(props, state);
}
}
If you can consider writing your component in a function instead of class, try React.memo.
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
As a second argument, you can pass a function, where you can use prevProps, nextProps, and return false, if you want it to render, or return true if you don't.
import React, { memo } from "react";
const ContentsListView = ({ selecting }) => {
return <div />;
};
const shouldComponentUpdate = (prevProps, nextProps) => {
if (nextProps.selecting) { return true; }
return JSON.stringify(prevProps) === JSON.stringify(nextProps)
}
const Component = memo(ContentsListView, shouldComponentUpdate);
I was looking for the same thing as you can see from my comment in January, I had eventually settled on using the shallow-compare library - https://www.npmjs.com/package/shallow-compare
export default class ContentsListView extends Component<Props> {
shouldComponentUpdate(nextProps, nextState) {
if (props.selecting) {
return false;
}
return shallowCompare(this, nextProps, nextState);
}
render() {
}
}
however extend from Component, not PureComponent
I'm hoping for some clarity on the use of React refs for calling a child function. I have a Parent component that's a toolbar with a few buttons on it, and in the child component I have access to a library's export functionality. I'd like to call this export function on a button click in the parent component. Currently I'm using React refs to accomplish this:
Parent.js [ref]
class Parent extends React.Component {
onExportClick = () => {
this.childRef.export();
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child ref={(node) => this.childRef = node;} />
</div>
)
}
}
Child.js [ref]
class Child extends React.Component {
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
This solution works fine, but I've seen a lot of disagreement on if this is the best practice. React's official doc on refs says that we should "avoid using refs for anything that can be done declaratively". In a discussion post for a similar question, Ben Alpert of the React Team says that "refs are designed for exactly this use case" but usually you should try to do it declaratively by passing a prop down.
Here's how I would do this declaratively without ref:
Parent.js [declarative]
class Parent extends React.Component {
onExportClick = () => {
// Set to trigger props change in child
this.setState({
shouldExport: true,
});
// Toggle back to false to ensure child doesn't keep
// calling export on subsequent props changes
// ?? this doesn't seem right
this.setState({
shouldExport: false,
});
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child shouldExport={this.state.shouldExport}/>
</div>
)
}
}
Child.js [declarative]
class Child extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport) {
this.export();
}
}
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
Although refs are seen as an "escape hatch" for this problem, this declarative solution seems a little hacky, and not any better than using refs. Should I continue to use refs to solve this problem? Or should I go with the somewhat hacky declarative approach?
You don't need to set the shouldExport back to false, you could instead detect the change:
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport !== this.props.shouldExport) {
this.export();
}
}
Then every toggle of the shouldExport would cause exactly one export. This however looks weird, I'd use a number that I'd increment:
componentWillReceiveProps(nextProps) {
if (nextProps.exportCount > this.props.exportCount) {
this.export();
}
}
I ran into the same problem in many occasions now, and since the React team doesn't encourage it i'll be using the props method for later development, but the problem is sometimes you want to return a value to the parent component, sometimes you need to check the child's state to decide whether to trigger an event or not, therefore refs method will always be my last haven, i suggest you do the same
I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.
I'm trying to use a stateful React component with ES6 but when I define a constructor the constructor will only be called once while the component is rendered multiple times (from its parent). Example shown below.
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("Creating sub component");
this.state = { count: props.count };
}
render() {
console.log("Rendering sub component", this.state.count);
return (<div>count: {this.state.count}</div>);
}
}
class App extends React.Component {
constructor(props) {
super(props);
console.log("Creating app");
this.state = { count: 0 };
this.tick = this.tick.bind(this);
setInterval(this.tick, 1000);
}
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
console.log("Rendering app", this.state.count);
return (<SubComponent count={this.state.count} />);
}
}
This will not update the rendered output (it will always be count: 0) but the logs will output:
Creating app
Rendering app 0
Creating sub component
Rendering sub component 0
Rendering app 1
Rendering sub component 0
Rendering app 2
Rendering sub component 0
Rendering app 3
Rendering sub component 0
...
Here's a JSFiddle: http://jsfiddle.net/jor0xu1a/1/
I'm aware that the example SubComponent doesn't need a state but I tried making it as simple as possible to show my problem.
What am I missing?
In SubComponent it is props not state - change it to this.props.count and this will work
I recommend to read Props in getInitialState Is an Anti-Pattern.
Basically, as few components as possible should have state. As the other answers already said, in your case you can just use this.props.count to refer to the current value. There doesn't seem to be any reason why SubComponent should have its own state.
However, if you really want to compute the component's state from the props it receives, it is your responsibility to keep them in sync, with the life cycle method componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
this.setState({count: nextProps.count});
}
You SubComponent should be:
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("Creating sub component");
}
render() {
return (<div>count: {this.props.count}</div>);
}
}
My bad, I thought that the constructor (or getInitialState for ES5) is called whenever the component is being re-rendered by the parent (I thought that the parent 're-creates' its children on render) but that's not always the case. I should had read up on it (url) and tried it with ES5 (jsFiddle) before thinking it was something I didn't understand with ES6 and creating a question here.
And yes, the example SubComponent should use this.props but my use case had actual stateful functionality in my real component. I created the example as I thought for some reason that the result weren't the expected outcome when using ES6 (but it was).
Thank you for you feedback!