React PureComponent is updating when it shouldn't be - javascript

I'm currently using, in my application, a PureComponent parent and children components. I expected a PureComponent to only update if it's state or props change. To ensure this, I had a child component log something everytime its componentDidUpdate() method was called. The application looks something like this:
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = {
item1: null,
item2: null,
list1: [],
list2: [],
}
render(){
let combinedList= [...this.state.list1, ...this.state.list2];
return(
<React.Fragment>
<Child myList={combinedList} />
<OtherChild item={this.state.item1} />
</React.Fragment>
);
The Child component looks something like this:
class Child extends React.PureComponent{
constructor(props){
super(props);
}
componentDidUpdate(){
console.log("Child updated");
}
render(){
return(
....
);
}
For example, when the state of item2 in Parent component changes, the Child component will log "Child updated". I'm a bit confused, since the child component doesn't receive item1 as a prop, and it's state isn't changed. Is this happening because the Parent component rerenders all of it's children? Or maybe because the shallow comparison Child does for its myList prop indicate that it has changed? Aside from writing my own shouldComponentUpdate() method, how can I prevent Child from rerendering everytime Parent rerenders?

Right now, in your render function for the Parent component, you're creating a new Array on every render with combinedList. PureComponent switches your shouldComponentUpdate() method to do a shallow compare to see if it should update. Since it's shallow, it's comparing references.
Since you're creating a new array on every render, it's going to be new every single time the Parent renders.
The solution here really depends on the full use-case that you have for combinedList. Looking at this example directly, I would recommend that you set combinedList in state. The other option would be to split your child component props up into a list1 and list2 prop instead of a myList prop.

Related

How this.setState() (react.Component method) has access of his child's class state?

Actually, I am learning to React JS, But I have confusion that how can a parent's class method has access to his child's class state. I searched a lot on this topic but in object-oriented programming parent class hasn't access to his child's properties (state). But in react js how setState() has to access to his child's class properties (state). It can be a stupid question please tell me how it happens?
Don't worry, this is a great question.
Props are passed by reference!
Here are some great answers from a StackOverflow post that answer your question with more specificity.
In reactjs, are props pass by value or pass by reference?
I think some potential nuance I can offer is that React emphasizes composition over inheritance. You should think of it as your parent component being composed of the child components. While the child components are given access to the parent state, they aren't inheriting it.
Understanding the Parent Child structure through the Component API may help, should you want to clear any confusion. Since you have mentioned Class Components, let me illustrate an example with the same.
You may send props from a Parent to a child through defining a property within the JSX insertion -
<Child uniqueProp="Text sent to Child" />
Conversely, it is tricky to access Child data in a Parent component. You'll have to follow these steps -
Define a callback function in the Parent Component to get data from the Child Component.
Pass the callback function in the Parent Component as a prop to the Child Component.
The Child Component calls the Parent callback function using props.
Example -
Parent Component
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Original Text"
}
}
handleCallback = (value) => {
this.setState({ text: value })
}
render() {
return (
<div>
<Child changeText={this.handleCallback} />
{this.state.text}
</div>
)
}
}
Child Component
class Child extends React.Component {
render() {
return (
<div>
<button onClick={() => this.props.changeText("Data from child")}>
Button
</button>
</div>
)
}
}
The reason it is set up that way is because props are passed by reference. Should you choose to change the parent's state traditionally state = {"something"} instead of setState({"something"}), the object will be modified in the child component as well but there will not be a re-render cycle. The user won't see UI changes until a manual refresh.
And when you use setState to change the parent's state (standard practice), the re-render cycle will be triggered as expected and the Child Component will reflect the UI changes.
Note: Be warned, the use of functional components is almost defacto now. Makes life easy with integrations of Hooks and data stores such as Redux in my opinion.

Updating Parent Component state from multiple child components' componentDidMount() synchronously

Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));

React pass Array from Parent state to child state

I have a parent component holding a state called nodes which is established as an array. I am trying to pass this.state.nodes to a child component and have that component have the array as part of the child component state, but it's not working. Here is the code:
<ChildComponent nodes={this.state.graphNodes}/>
This is in the return statement. After some testing/console logging I know the prop is being passed.
In the child component, I have:
this.state = {
nodes: this.props.nodes,
links: [],
totalNodes: [],
totalLinks: []
}
But when I try to reference it or map it or do anything with it in the child component render or return it's telling me it's an empty array.
Since graphNodes is in a component state, i think its initial value should be an empty array, and you are initializing state with props.nodes at constructor, since constructor renders only once at initialization stage of the component at that moment graphNodes must be an empty array, after you change the parent component state, the state in the child component will not update. That makes graphNodes state always an empty array.
Why don't you use props directly, if you don't have to change the graphNodes values in the child component you can use it directly from props.
If you really want map props to state, you have to do it in componentDidUpdate lifecycle method with proper conditions
All of the comments were dead on. Using this.props.nodes directly instead of trying to put it in child state was the perfect solution. Thanks to all who replied!

Accessing props in React constructor

I have a problem when I try to access parent's props value in the child class.
I am trying to initialize the child props with the parent's props.
But the following code shows me empty string of value.
export default class Child extends React.Component {
constructor(props) {
super(props);
this.state = { value: props.value };
}
...
In other class I call this Child as follows:
const issue = this.state.issue;
...
<Child value={issue.number} />
...
I checked the number value before Child class is constructed (it shows me correct values), while it is construced, the value becomes empty..
For test, I used primitive number value, and it works.
Seems like some kind of link to issue.number is disconnected when Child is contructed.
Any clue?
This could easily happen. For example, issue.number becomes a number after AJAX re-rendering. In this case you have to do follow (in your parent component):
render() {
const issue = this.state.issue
if (issue.number) {
return <Child value={issue.number} />
}
}
Ah, I just figured it out.
It is related to Lifecycle.
The number value is not loaded when I render Child.
So, I need to figure it out how to update the number in Child with my intention.

How can I create shared state for HOC?

I have created LoadBookHOC which is wrapped with BookDetails and BookSummary component.
LoadBookHOC.js
const LoadBookHOC = InnerComponent => class LoadBook extends React.Component {
constructor(){
super();
this.state={
book: []
};
}
set= obj => this.setState(obj);
render(){
return(
<InnerComponent
{...this.props}
hocState={this.state}
hocSetState={this.set}
/>
);
}
}
BookDetails.js
this.hocSetState({book: <new data>});
BookSummary.js
this.hocSetState({book: <new data>});
Whenever this.props.hocState.book called in BookDetails or BookSummary, I need to get the same data. I mean all the InnerComponent should get the same update. Is there any other way instead of redux (makes use to write lots of code)?
update: How can I make this HOC acting like a provider or context or shared state? which one is suitable for this scenario?
HOC is not for sharing state , but sharing functionality. If you are having two instances for eg: <BookDetails/> and <BookSummary/> in same page , both enhanced with same HOC, there will be two instances of book array. So updating in one component wont be visible to other one.
For sharing state you should as you said use Redux or store state in common Parent component.

Categories