React component not re-rendering with new props unless I change key - reactjs

I'm making an app that displays a lot of graphs, so in order to prevent unnecessary re-rendering I've been going through my components and seeing where I can implement shouldComponentUpdate to improve efficiency. But after doing so in some of the higher up components I'm getting a weird bug with the graph component itself. I have the generic <Graph/> parent component with this render:
render() {
return(
<div>
{ this.getGraph() }
</div>
);
}
and the getGraph() essentially returns this:
return React.createElement(graphComponentMapping[graphType], {data:this.state.data,...this.state.configuration});
where graphComponentMapping[graphType] is a <Table/> component (or various other ones, but we'll focus on table for now). Now in that <Table/> component I render this:
return(
<ReactTable {...this.getParameters()} data={data} columns={columns} />
);
and the getParameters() function essentially just looks through the configuration prop and gets all the properties relevant to the React Table component.
Now, when I initially render the <Table/>, it has the correct configuration (i.e. if I change the initial config the table matches that). But when I update the configuration prop in the <Graph/> parent, the table doesn't update accordingly. The render function in <Table/> is called again, and if I print this.props in the table's render I can see that various configuration properties have changed, e.g. this.props.defaultPageSize has changed from say 5 to 3, but the table doesn't re-render to reflect this. Even in the browser's React dev tools I can see the props have changed.
If I force a complete a re-render of the element by using a random key, e.g.
return(
<ReactTable {...this.getParameters()} data={data} columns={columns} key={Math.random()} />
);
then it works, and the table updates when I pass new configuration props. Why doesn't it update when receiving new props, only when I force it to completely re-render?

I have faced such problem but that was primarily the way my redux store was setup.
Redux object is reference based so even just updating the properties, it wasn't telling my React component that something has changed. To solve this, I had to create a new object and update value there and then use it, which solved the issue.
this could be the reason they key having a random number is causing your whole component to re-render.

Related

Conditionally wrapping React component without losing children state

I was wondering if it's possible to conditionally wrap a React component without losing the state of the children.
In the sandbox, you can see when clicking the Increment button a few times, followed by the "Wrap children" or "Unwrap children" action, the counter is reset.
https://codesandbox.io/s/fervent-herschel-f62e2?file=/src/App.js
The relevant code can be seen here:
const ConditionalWrap = ({ condition, children }) => {
return (
<Fragment>
{condition ? (
<div>{children}</div>
) : (
children
)}
</Fragment>
);
};
Can anyone point me to a resource why this is happening? I guess the parent tree needs to stay the same, I'd just like to know why.
Is there some way to keep the state of all the children when conditionally wrapping them in an element or context provider?
There's explanations of the reconciliation in the react documentation.
React sees two elements of different types and so tears down the old and puts in a new.
There isn't a way to keep the state of all the children. It might be a good idea to either keep them wrapped in the same element. Or move the state into context, redux, or props that you pass down.
There is a library someone built for the purpose of reparenting: https://github.com/httptoolkit/react-reverse-portal

React setState on array of objects rerenders every component

I'm using React, and want to update array of objects in a state. The overall answer in internet was to use a similar approach to below:
const newState = this.state.elements.slice().concat({key: 'val'});
this.setState({elements: newState});
But the problem I encountered with this issue is that when these data are binded into a render function and components, it re-renders every component.
Example include (map is used from lodash to retrieve index while mapping):
render() {
return (
<div>
{map(this.state.elements, (el, index) => <Component key={`el-${index}`} el={el} />)}
</div>
);
}
Even though, the array order doesn't change and the key's of the components doesnt change, everytime the state changes, it re-renders and mounts the component from scratch.
Is there a possible best practice solution to this issue?
Best,
Y
Whats happening is that when you call setState the React Virtual Dom diffs state and calls render again. Render calls to a Map function which renders each component. Each component is thrown away and redrawn because the parent component changes.
This is normal and expected behavior by React. It's doing what you're asking of it, you changed state, it redraws the components.
But your question is how do I append to the list of components my new component without redrawing?
I think the issue is that you're relying on the unique key of react to be the index of the array item.
<Component key={`el-${index}`}
That will forever change every time you update state. What you need to do is use something like name, or string or a generated key or something from the data.
You are using just map function index value and of course after adding new element to the list will trigger a re-render because key value for each of them was changed.
if you did something like this:
<Component key={`el-${item.id}`}
Where item id is a constant but unique value, like a primary key in a database table. It would not redraw. The key should have a deterministic value.
See React Docs and this other post for more details.

Refresh only the component that changed from the parent

I have a react component that is a list, let's call it List, it has a state named items that is an array of object of the form :
{ id: String, name: String }
its render function is something like
render () {
return (
<ul>
{ items.map((item) => {
return (
<Item key={item.id} item={item} />
);
})}
</ul>
);
}
with Item being a react component too, of course.
So now, my question: let's say that at one point, I know that the data changed (it can only be a name change, of course), how can I trigger Item to refresh based on its key from the parent?
I found two solutions so far :
refreshing the whole state, but I don't like it cause it seems overkill.
maintaining an array of references to the child components, but I don't like it cause it feels like a waste of memory.
so what's a 'reactier' way of doing this?
If the items array changed then the render of the Parent is called, since the keys Item components are same, the component is not re-created but its componentWillReceiveProps function is called and the Item component is re-rendered.
What you can do as an optimization is to create a Item component as a Pure component which you can do by extending React.PureComponent. React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects so if the Props haven't changed a rerender on child is not called.
According to the DOCS:
If your React component’s render() function renders the same result
given the same props and state, you can use React.PureComponent for a
performance boost in some cases.
React.PureComponent’s shouldComponentUpdate() only shallowly
compares the objects. If these contain complex data structures, it may
produce false-negatives for deeper differences. Only extend
PureComponent when you expect to have simple props and state, or use
forceUpdate() when you know deep data structures have changed. Or,
consider using immutable objects to facilitate fast comparisons of
nested data.
Furthermore, React.PureComponent’s shouldComponentUpdate() skips
prop updates for the whole component subtree. Make sure all the
children components are also “pure”.
By implementing shouldComponentUpdate(nextProps, newState) in your React components you can calculate whether or not the component needs to update. So if you send a whole new object in, this should trigger already in its default implementation (they don't do a deep-compare), but if the property input.name is changed then it will not detect this. So you could implement this yourself like this:
shouldComponentUpdate(nextProps, newState) {
return this.props.name === nextProps.name; // use === to compare types too!
}
Now I'm a bit rusty on the JavaScript (I prefer TypeScript), so I am not sure if this is the correct way to compare strings.
Using the shouldComponentUpdate() method, you can specify whether or not a component should update based on the current props and the new props which are provided as:
shouldComponentUpdate(nextProps){
return this.props.item.name !== nextProps.item.name;
}
Here's a JsFiddle with a demo: https://jsfiddle.net/Vorcan/Lakj6qwb/
Usually, you have to create a new object instance using something like Object.assign().
Other than the docs, another good resource to check out shouldComponentUpdate is https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/using_should_component_update.html

React: Parent component re-renders all children, even those that haven't changed on state change

I haven't been able to find a clear answer to this, hope this isn't repetitive.
I am using React + Redux for a simple chat app. The app is comprised of an InputBar, MessageList, and Container component. The Container (as you might imagine) wraps the other two components and is connected to the store. The state of my messages, as well as current message (the message the user is currently typing) is held in the Redux store. Simplified structure:
class ContainerComponent extends Component {
...
render() {
return (
<div id="message-container">
<MessageList
messages={this.props.messages}
/>
<InputBar
currentMessage={this.props.currentMessage}
updateMessage={this.props.updateMessage}
onSubmit={this.props.addMessage}
/>
</div>
);
}
}
The issue I'm having occurs when updating the current message. Updating the current message triggers an action that updates the store, which updates the props passing through container and back to the InputBar component.
This works, however a side effect is that my MessageList component is getting re-rendered every time this happens. MessageList does not receive the current message and doesn't have any reason to update. This is a big issue because once the MessageList becomes big, the app becomes noticeably slower every time current message updates.
I've tried setting and updating the current message state directly within the InputBar component (so completely ignoring the Redux architecture) and that "fixes" the problem, however I would like to stick with Redux design pattern if possible.
My questions are:
If a parent component is updated, does React always update all the direct children within that component?
What is the right approach here?
If a parent component is updated, does React always update all the direct children within that component?
No. React will only re-render a component if shouldComponentUpdate() returns true. By default, that method always returns true to avoid any subtle bugs for newcomers (and as William B pointed out, the DOM won't actually update unless something changed, lowering the impact).
To prevent your sub-component from re-rendering unnecessarily, you need to implement the shouldComponentUpdate method in such a way that it only returns true when the data has actually changed. If this.props.messages is always the same array, it could be as simple as this:
shouldComponentUpdate(nextProps) {
return (this.props.messages !== nextProps.messages);
}
You may also want to do some sort of deep comparison or comparison of the message IDs or something, it depends on your requirements.
EDIT: After a few years many people are using functional components. If that's the case for you then you'll want to check out React.memo. By default functional components will re-render every time just like the default behavior of class components. To modify that behavior you can use React.memo() and optionally provide an areEqual() function.
If a parent component is updated, does React always update all the direct children within that component?
-> Yes , by default if parent changes all its direct children are re-rendered but that re-render doesn't necessarily changes the actual DOM , thats how React works , only visible changes are updated to real DOM.
What is the right approach here?
-> To prevent even re-rendering of virtual DOM so to boost your performance further you can follow any of the following techniques:
Apply ShouldComponentUpdate Lifecycle method - This is applied only if your child component is class based , you need to check the current props value with the prev props value ,and if they are true simply return false.
Use Pure Component -> This is just a shorter version to above method , again works with class based components
Use React memo -> this is the best way to prevent Rerendering even if you have functional components ,you simply need to wrap your components export with React.memo like : export default React.memo(MessageList)
Hope that helps!
If parent component props have changed it will re-render all of its children which are made using React.Component statement.
Try making your <MessageList> component a React.PureComponent to evade this.
According to React docs: In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component. check this link for more info
Hope this helps anyone who is looking for the right way to fix this.
If you're using map to render child components and using a unique key on them (something like uuid()), maybe switch back to using the i from the map as key. It might solve the re-rendering issue.
Not sure about this approach, but sometimes it fixes the issue

How do I manage state on a React component that can have state changed from the parent or from events upon it?

I'm trying to make a custom checkbox component (a three-state, actually, but that's irrelevant except to say that I'm not just using an INPUT), and I'm not sure how I can make it able to change "checkedness" from clicks on itself and from a value-set coming down from the parent.
Currently, I have it working as a self-sufficient component that takes an onChange prop with the handler callback that it calls to send the value the parent component after clicks. It uses a state to store the checkedness, which is referenced by the display.
If it were merely a display of checkedness, with value being managed from outside, I'd use props, naturally. If it were only a self-sufficient checkbox component that took an initial value then only responded to clicks, I'd use state, like I am, but my problem is that I want it to be clickable to turn itself on and off, and allow the parent to turn it on and off as well.
I'm a beginner to React and the "React way of thinking" so I suspect I'm just approaching this wrong. I kind of get the impression that the proper way to do this would be for it to be a display-only component that passed clicks up to the parent to deal with, and in turn received props updates for value changes down from the parent, but that would make the component far less reusable, to my mind.
So how would I go about making a checkbox change from both internal and parent sources?
Relevant links are welcome, as well.
You may treat the checkbox as a dumb component, which means that it doesn't hold any internal states, but only receives data from outside via props and then render them. You can see the detailed definition of dumb components here.
Meanwhile, when the checkbox is clicked, such event will be handled by the component's parent, or event ancestors, this is called inverse data flow, which is described in Facebook's Thinking in React blog post.
Moreover, to decide which component should hold certain states, I find the following guidelines very useful:
Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand, so follow these steps to figure it out:
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should own the state.
If you can't find a component where it makes sense to own the state, create a new component simply for holding the state and add it somewhere in the hierarchy above the common owner component.
The pseudo-code snippet:
const Checkbox = React.createClass({
// Propagate the event to parents
onClick(evt) {
if (this.props.handleClick) {
this.props.handleClick({checked: evt.target.value});
}
},
render() {
return (
this.props.checked ?
<Input onClick={this.onClick} type="checkbox" label="Checkbox" checked></Input> :
<Input onClick={this.onClick} type="checkbox" label="Checkbox"></Input>
);
}
});
const Container = React.createClass({
handleClick(evt) {
// Change the container's state, which eventually changes the checkbox's view via props.
this.setState({checked: evt.checked});
},
render() {
return (
<div><Checkbox checked={this.state.checked} handleClick={this.handleClick}></Checkbox></div>
);
}
});
You change it from only the parent.
class ParentComponent extends React.Component{
handleChildCheck(){
this.setState({
childChecked: !this.state.childChecked
})
}
render(){
return(
<ChildComponent checked={this.state.childChecked} handleCheck={this.handleChildCheck.bind(this)} />
)
}
}
Now if you wish to control the checked state from the <ChildComponent/> just call this.props.handleCheck() from the <ChildComponent/>
This way the controls will always be available within the <ChildComponent/> via this.props.handleCheck() within the <ParentComponent/> via this.handleChildCheck().

Resources