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
Related
I use the useEffect hook inside functional components with a dependency so that dependency changes , useEffect function will re-run like this :
const [show, setShow] = React.useState(false);
React.useEffect(() => {
console.log("Do something")
} , [show]);
I wanted to know what is available in react's class component to do exactly like this ?
Is there any lifecycle method to have this functionality ?
you can use combination of componentDidMount and componentDidUpdate:
componentDidMount(){ //use this method if you want to trigger the side effect first time
console.log("Do something")
}
componentDidUpdate(prevProps,prevState) {
if (this.state.show !== prevState.show) {
console.log("Do something");
}
}
To control your component use shouldComponentUpdate (link for the article). It has 2 arguments nextProps and nextState. You can compare this.state.field and nextState.field and if they are different make side effect:
class ClickButton extends React.Component {
constructor(props) {
super(props);
this.state = {class: "off", label: "press"};
this.press = this.press.bind(this);
}
shouldComponentUpdate(nextProps, nextState){
if(nextState.class !== this.state.class){
return true
}
return false;
}
press(){
var className = (this.state.class==="off")?"on":"off";
this.setState({class: className});
}
render() {
return <button onClick={this.press} className={this.state.class}>{this.state.label}</button>;
}
}
If ypu return true from this method, it says React that component should update, false in other way, Component won't update.
Also you can extends from PureComponent (PureComponent), it will be automatically follow props and state:
class ClickButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {class: "off", label: "press"};
this.press = this.press.bind(this);
}
press(){
var className = (this.state.class==="off")?"on":"off";
this.setState({class: className});
}
render() {
return <button onClick={this.press} className={this.state.class}>{this.state.label}</button>;
}
}
But it makes a superficial comparison (by reference). If you have nested fields in you state, and they are changing, PureComponent doesn't rerender Component.
There are other methods like componentDidUpdate (link) and componentDidMount (link). First, called when component rerender:
componentDidUpdate(prevState) {
if (this.state.userID !== prevState.userID) {
this.fetchData(this.state.userID);
}
}
Talking about second one, it will be called when component set in the DOM.
In your case use componentDidUpdate
Is there any side effect I do not see by doing this ?
class App extends React.Component {
hello() {
console.log("hello")
}
render() {
return <Layout app={this}>
}
}
So later on I can refer to this.props.app.hello (and others) from Layout ?
This is not safe.
React will not know how to watch for changes, so you may miss re-renders. React uses === to check for state changes, and App will always be === to App, even when state or properties change.
Take this example:
class App extends React.Component {
constructor(props) {
super(props);
this.setState({text: 'default value'});
}
hello() {
this.setState({...this.state, text: 'new value'});
}
render() {
return (
<div onClick={this.hello}>
<Layout app={this}>
</div>
);
}
}
class Layout extends React.Component {
render() {
return <div>{this.app.state.text}</div>
}
}
When you click on the parent div, this.hello will be called, but the child component will not detect the state update, and may not re-render as expected. If it does re-render, it will be because the parent did. Relying on this will cause future bugs.
A safer pattern is to pass only what is needed into props:
class App extends React.Component {
//...
render() {
return (
<div onClick={this.hello}>
<Layout text={this.state.text}>
</div>
);
}
}
class Layout extends React.Component {
render() {
return <div>{this.props.text}</div>
}
}
This will update as expected.
Answer
There's nothing wrong in passing functions as props, as I can see in your example, the only thing you have to do is make sure your function is bound to the current component like the following example
Reference
React: Passing Functions to Components
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.
I want to clone/extend a React component (without knowing if it is statefull or stateless) and pass it props:
const Foo = (props) => {
return (
<div>foo</div>
);
}
class Bar extends React.Component {
render() {
return (
<div>bar</div>
)
}
}
The problem is, these two variables Foo and Bar should be handled differently:
const FooExtended = (props, context) => {
return Foo(_.extend(props, additionalProps), context);
}
class BarExtended extends Bar {
constructor(props, context) {
super(_.extend(props, additionalProps), context);
}
}
And there is no simple way to know if a variable Component is Stateless or Statefull without doing hacky toString regex tests.
React.cloneElement/createElement fails on these giving me the following error:
React.createElement: type is invalid -- expected a string (for
built-in components) or a class/function (for composite components)
but got: object. You likely forgot to export your component from the
file it's defined in.
So is there a simple way that I can do just cloneComponent(originalComponent, additionalProps)?
And there is no simple way to know if a variable Component is Stateless or Statefull [...]
And I think this is one of the reasons why it was required to extend React.Component at some point, to make it easier to distinguish between those two. Because React itself has to be able to distinguish between them since classes cannot be instantiated without new.
You could do the following:
function cloneComponent(originalComponent, additionalProps) {
if (originalComponent.prototype instanceof React.Component) {
return class extends originalComponent {
constructor(props, context) {
super(_.extend(props, additionalProps), context);
}
};
}
return (props, context) => {
return originalComponent(_.extend(props, additionalProps), context);
};
}
Because Foo.protoype instanceof React.Component is true.
However, I think it is more common to do something like this instead:
function addProps(Component, additionalProps) {
return props => <Component {...props} {...additionalProps} />;
}
Then there is no need to distinguish between stateful and stateless components.
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.