React.PureComponent doesn't work when the Component has children? - performance

It seems a common technic to use PureComponent to improve rendering perf in React. However, it seems not the case when using PureComponent who has children as props.
class App extends React.PureComponent {
render() {
console.log('re-render')
return <div>{this.props.children}</div>
}
}
const render = () => {
ReactDOM.render(
<App>
<div />
</App>,
document.getElementById('app')
)
setTimeout(render, 1000)
}
render()
The result is that console keeps logging 're-render' every 1s. It seems the children(<div />) is the only prop of above App component and never changes, why App still gets re-rendered?
Note: in case of any confusion, the question is the same as, why SCU(shouldComponentUpdate) hook of PureComponent above return true since no props seems changed?

What happen here is you're actually calling ReactDOM.render(), Page (or App, I suppose you have a typo here) component is gonna trigger its render() function regardless of using Component or PureComponent.
The way PureComponent can help to reduce unnecessary rendering is when there is a prop change, PureComponent will do a shallow comparison on this.props and nextProps to determine if this PureComponent needs to call render().
I just made this example for you:
class App extends React.PureComponent {
state = {value: 0}
componentDidMount() {
setInterval(() => {
this.setState({value: Math.random()})
}, 1000)
}
render() {
return (
<div>
<PureChild value="fixed value"/>
<ImpureChild value="fixed value"/>
</div>
)
}
}
class PureChild extends React.PureComponent {
render() {
console.log('rendering PureChild')
return <div>{this.props.value}</div>
}
}
class ImpureChild extends React.Component {
render() {
console.log('rendering ImpureChild')
return <div>{this.props.value}</div>
}
}
Pay attention to this few things:
Both children are receiving a fixed props ("fixed value" string)
Every 1 second, the parent <App /> change value state, thus it re-renders, causing all its children to re-render as well.
But since <PureChild /> is a PureComponent, it does a shallow comparison on its old props and incoming new props, and notices both props are "fixed value", and therefore it doesn't trigger render!
If you run this code and open up console, you'll see only 'rendering ImpureChild' every 1s, but 'rendering PureChild' will only appear once.

console.log(<div /> === <div />) // false
On every rerender of <App />, a new React Element was created by React.createElement(div, null), thus this.props.children will be different from nextProps.children though they look the same in JSX.
In fact, the real problem is that the reference(otherwise value if is primitive type) of props.children changes every time the parent re-renders and React.PureComponent compares props by reference embracing immutability.

Now as per the documentation of ReactDOM
ReactDOM.render() controls the contents of the container node you pass
in. Any existing DOM elements inside are replaced when first called.
Later calls use React’s DOM diffing algorithm for efficient updates.
ReactDOM.render() does not modify the container node (only modifies
the children of the container). It may be possible to insert a
component to an existing DOM node without overwriting the existing
children.
ReactDOM from second time onwards, just updates the React component with the diffing algorithm it uses else where, so Its not the ReactDOM, that causes the re-render then. You can verify this by add a componentWillMount method in the App Component and check that it is only called once
Now coming to the PureComponent. The docs state that
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
So here is the catch, PureComponent may return false negatives for deeper differences. So when you try to compare this.props.children with nextProps.children for equality you will find that it returns false and hence the re-render is triggered again
Check this CodeSandbox

As per documentation of the React.PureComponent
1). PureComponent implement shouldComponentUpdate() with a shallow props and state comparison, will check whether page needs to be re-render
2). If there is complex objects in props or state, then PureComponent will give false positive result, must have to run force update
3). Change in parent component will not update children, so PureComponent's children should also be PureComponent

Related

Using React Hooks to Show Component Render Time and Date

My application contains a view that is a functional react component. I'm trying to add a timestamp at the bottom of the view displaying the date and time when the user navigated to this view. I'm wondering what would be appropriate way to implement this using hooks. I guess one option would be to use const [date] = useState(new Date()) and simply provide no setter for the state since it would never change. However, this makes me wonder if useState is the appropriate hook to begin with. Should I just do const date = new Date() in the component body or would that have some unexpected side effects?
For what you intend to accomplish, the code below would work just fine:
// solution # 1
function MyComponent() {
const [time, setTime] = useState(new Date());
return (
<div>
View created at {time.toLocaleTimeString()}
</div>
)
}
However, it is important to know that it is not following React rules. That is because we are calling new Date() from inside the component, making it impure. React expects the components to be pure functions. This means that it should return the same thing for the same input. But React cannot guarantee that a function component doesn't have side effects, and that is why that first solution would work just fine.
According to this gist, a better approach for this task would be:
// solution # 2
function MyComponent() {
const [time, setTime] = useState<Date>();
useEffect(() => {
setTime(new Date());
}}, [])
return (
<div>
View created at {time && time.toLocaleTimeString()}
</div>
)
}
Roughly speaking, all side effects should live inside useEffect. So, things like calling an external API or calling impure functions (such as Math.random() or Date.now()) should be there. From my understanding, this solution would be more appropriate.
If we are not obeying React guidelines, the library might not work as advertised. Before version 18, React had only synchronous rendering. This means that the moment the render phase is kicked off (either by a initial render or by a state update), nothing could interrupt it from committing those updates to the browser. In that situation, I don't think the first solution I presented would be problematic. But, in React 18, we have the concurrent mode features by which rendering can be interrupted before the diffs are committed to the screen. With that, React can be more intelligent about how it breaks up all the work it needs to do (for example, we can now define high-priority updates).
In order for these new APIs to work properly, React assumes that the components are pure functions, which means that it has no side effects. These APIs are very new and there are still many more additional features coming in. I am not quite sure about how sensitive they are to function impurity, but I believe our second solution (with all side effects living inside the useEffect) is a better bet for this task.
The following seems to work. Still not sure if this is optimal though.
import React from 'react'
export function View(_props) {
const [viewCreated] = React.useState(Date.now())
return (
<div>
View created {String(new Date(viewCreated))}
</div>
)
}
useState(new Date()) is better. Here is complete component
import React, { useState , useEffect } from 'react'
export const DateTime = () => {
var [date,setDate] = useState(new Date());
useEffect(() => {
var timer = setInterval(()=>setDate(new Date()), 1000 )
return function cleanup() {
clearInterval(timer)
}
});
return(
<div>
<p> Time : {date.toLocaleTimeString()}</p>
<p> Date : {date.toLocaleDateString()}</p>
</div>
)
}
export default DateTime

react-redux can update child smart components without updating parents?

Imagine the following React structure:
SmartComponentA -> DumbComponentB -> SmartComponentC
Also imagine that SmartComponentA and SmartComponentC each get connected to different slices of the state in their mapStateToProps functions.
Lastly, imagine that we put a console.log in the render method of each of these components.
When I actually try this, on the first render, I see that all components log as expected. But then if I change the data for SmartComponentC, I only see a single log message (C's log message), and I don't see SmartComponentA or DumbComponentB logging anything. How is that possible? How is react-redux getting React to update a child without updating its parents?
I would have assumed that the overriding of shouldComponentUpdate inside of the connect method would mean SmartComponentA would not get re-rendered (since its slice of the state didn't change), and therefore would cause a short-circuiting that would prevent SmartComponentC from getting re-rendered. While connect's implementation is not the same as the pure render mixin, both work by changing shouldComponentUpdate, but the pure render docs clearly state that React will "bail out" (as they put it) if a parent doesn't need to re-render:
for C2's subtree and C7, it didn't even have to compute the virtual DOM as we bailed out on shouldComponentUpdate.
source
If my question still isn't clear, here is sort of pseudo-code for the setup, and I'm asking why I can keep typing in C's input and it only log's C's messages to the console and not A's and B's (why is it not short-circuiting)?
//////////////////////////////////////////////
const SmartComponentA = (props) => {
console.log('rendering SmartComponentA');
return <DumbComponentB bData={props.bData} />;
};
const mapStateToProps = (state) => { bData: state.bData };
export default connect(mapStateToProps)(SmartComponentA);
//////////////////////////////////////////////
const DumbComponentB = (props) => {
console.log('rendering DumbComponentB');
return (
<div>
{props.bData}
<SmartComponentC />
</div>
);
}
export default DumbComponentB;
//////////////////////////////////////////////
const SmartComponentC = (props) => {
console.log('rendering SmartComponentC');
return (
<div>
<input value={props.cValue} onChange={props.changeCValue} />
</div>
);
}
const mapStateToProps = (state) => { cValue: state.cValue };
export default connect(mapStateToProps, { changeCValue })(SmartComponentC);
//////////////////////////////////////////////
On the first render I see all log messages, then if I keep typing in the input, I only see C's log message each time I press a key.
Prop changes trigger the React component lifecycle which will typically trigger the lifecycle of each child component unless -- as you observe, the process can be short-circuited by shouldComponentUpdate.
But prop changes aren't they only thing that triggers the component lifecycle -- state changes do too. And that's how connect function works. The Connect component subscribes to the store and on any store change checks to see if it will update the smart component's props (based on mapStateToProps). If so it will set it's own state triggering the lifecycle functions for the Connect component and it's child.

Redux Saga Behavior of Component Life-cycle

This is a general question. I have a redux saga yielding calls that update the store every x mins and show the store gets updated appropriately in the redux dev tool. In the render method of my component if I click before the data I will get a spinner and if I click after the component will render; HOWEVER, in the components class the life cycle "componentWillUpdate" or "componentWillReceiveProps" shows the connected piece in redux store as undefined in either method yet the render is able to pass the correct props; what the cluck? I'll head back to the docs but this seems odd.
...
//dont usually use this for redux
componentWillReceiveProps(){
console.log(dailyOperations) // nothing here
}
componentWillUpdate(){
console.log(dailyOperations) // nothing here
}
render(){
if (dailyOperations === undefined) {
return (<SpinnerThing />)
else
return (<SomeDisplayComponent data={dailyOperations} />) //Data is here
}
I couldn't at the time account for it but now it makes sense and if it helps anyone else great. It will not be there on the update for the connected redux state but through "componentWillUpdate(nextProps)" & nextProps will have it.

Which component should control the loading state of a lower component?

Let's say I have these components:
Translator
TranslationList
Translator determines translation context, has translate function.
TranslationList must show these "visual states": loading, result list, no results.
The Translator moves around the page (one instance): on focusing an input, it moves "below" it and gives a dropdown with suggestion.
So each time it moves, it has to:
Show that it's loading translations
Show translation list or no results message.
So my question is:
Which component should control the "loading" visual state?
If the Translator component controls it, it has to pass loading=true translations=[] as props to Translation list. Then later it has to rerender it again with new props loading=false translations=[...]. This seems a bit counter-intuitive, because loading feels like the state of the TranslationList component.
If we the TranslationList component has loading state, then it also has to have a way to translate things, meaning that I have to pass translate function as prop. I would then hold translations and loading as state. This all gets a bit messy, since it must now also receive string to translate, context.
I also don't want to have separate components for loading message, no results message. I'd rather keep these inside the TranslationList, because these 3 share that same wrapper <div class="list-group"></div>
Perhaps there should be one more Component in between these two components, responsible only for fetching translation data?
Translator component should control the loading state of a lower component list component. hold the loading and translating logic but with help by wrapping it in a high order component where you should put most of the logic. link for HOC https://www.youtube.com/watch?v=ymJOm5jY1tQ.
const translateSelected = wrappedComponent =>
//return Translator component
class extends React.Component {
state = {translatedText: [], loading:true}
componentDidMount(){
fetch("text to translate")
.then(transText => this.setState({translatedText: transText, loading: false}))
}
render() {
const {translatedText} = this.state
return <WrappedComponent {..this.props} {...translatedText}
}
}
const Translator_HOC = translateSelected(Translator);
You could introduce a Higher Order Component to control the switching of the loading state and the TranslationList. That way you separate the loading display away from your TranslationList as being it's concern. This also allows you to use the HOC in other areas.
The Translator can act as "container" component which does the data fetching/passing.
For example:
// The Loadable HOC
function Loadable(WrappedComponent) {
return function LoadableComponent({ loaded, ...otherProps }) {
return loaded
? <WrappedComponent {...otherProps} />
: <div>Loading...</div>
}
}
// Translation list doesn't need to know about "loaded" prop
function TranslationList({ translations }) {
return (
<ul>
{
translations.map((translation, index) =>
<li key={index}>{translation}</li>
)
}
</ul>
)
}
// We create our new composed component here.
const LoadableTranslationList = Loadable(TranslationList)
class Translator extends React.Component {
state = {
loaded: false,
translations: []
}
componentDidMount() {
// Let's simulate a data fetch, typically you are going to access
// a prop like this.props.textToTranslate and then pass that to
// an API or redux action to fetch the respective translations.
setTimeout(() => {
this.setState({
loaded: true,
translations: [ 'Bonjour', 'Goddag', 'Hola' ]
});
}, 2000);
}
render() {
const { loaded, translations } = this.state;
return (
<div>
<h3>Translations for "{this.props.textToTranslate}"</h3>
<LoadableTranslationList loaded={loaded} translations={translations} />
</div>
)
}
}
ReactDOM.render(<Translate textToTranslate="Hello" />)
Running example here: http://www.webpackbin.com/NyQnWe54W

Function.bind used in event binding will always re-render because it is not pure

Working on render performance on React, wonder what is the best way to tackle this performance issue. (The code is overly simplified for clarity)
var TodoList = React.createClass({
getInitialState: function () {
return { todos: Immutable.List(['Buy milk', 'Buy eggs']) };
},
onTodoChange: function (index, newValue) {
this.setState({
todos: this.state.todos.set(index, newValue)
});
},
render: function () {
return (
this.state.todos.map((todo, index) =>
<TodoItem
value={todo}
onChange={this.onTodoChange.bind(null, index)} />
)
);
}
});
Assume only one single todo item has been changed. First, TodoList.render() will be called and start re-render the list again. Since TodoItem.onChange is binding to a event handler thru bind, thus, TodoItem.onChange will have a new value every time TodoList.render() is called. That means, even though we applied React.addons.PureRenderMixin to TodoItem.mixins, not one but all TodoItem will be re-rendered even when their value has not been changed.
I believe there are multiple ways to solve this, I am looking for an elegant way to solve the issue.
When looping through UI components in React, you need to use the key property. This allows for like-for-like comparisons. You will probably have seen the following warning in the console.
Warning: Each child in an array or iterator should have a unique "key" prop.
It's tempting to use the index property as the key, and if the list is static this may be a good choice (if only to get rid of the warning). However if the list is dynamic, you need a better key. In this case, I'd opt for the value of the todo item itself.
render: function () {
return (
this.state.todos.map((todo, index) => (
<TodoItem
key={todo}
value={todo}
onChange={this.onTodoChange.bind(null, index)}
/>
))
);
}
Finally, I think your conjecture about the nature of the onChange property is off the mark. Yes it will be a different property each time it is rendered. But the property itself has no rendering effect, so it doesn't come into play in the virtual DOM comparison.
UPDATE
(This answer has been updated based on the conversation below.)
Whilst it's true that a change to a non-render based prop like onChange won't trigger a re-render, it will trigger a virtual DOM comparison. Depending on the size of your app, this may be expensive or it may be trivial.
Should it be necessary to avoid this comparison, you'll need to implement the component's shouldComponentUpdate method to ignore any changes to non-render based props. e.g.
shouldComponentUpdate(nextProps) {
const ignoreProps = [ 'onChange' ];
const keys = Object.keys(this.props)
.filter((k) => ignoreProps.indexOf(k) === -1);
const keysNext = Object.keys(nextProps)
.filter((k) => ignoreProps.indexOf(k) === -1);
return keysNext.length !== keys.length ||
keysNext.some((k) => nextProps[k] !== this.props[k]);
}
If using state, you'll also need to compare nextState to this.state.

Resources