How to avoid unnecessary re-rendering of React Component - javascript

I've been learning new features of React 16.8. I believe React's Pure Component should automatically avoid unnecessary re-render operations.
In the following example, the App itself is a stateless component. I use useState to maintain two state objects text and nested: {text}.
There are 3 tests. The first 2 tests work. No matter how many times, I change the state, no re-render operation will be required.
Now, the third test tries to set the state of text with the same string value, but the reference is different. I expect nothing to be re-rendered, but actually, the <Headline/> will be re-rendered.
Shall I use certain memorize technique to avoid? I feel it will be too much code to archive that. And programmer must be very careful to write high-quality React code. ..
class Headline extends React.PureComponent {
render() {
const {text} = this.props;
return <h1>{text} (render time: {Date.now()})</h1>;
}
}
const simpleText = 'hello world'
const App = () => {
const [text, setText] = React.useState(simpleText)
const [nested, setNested] = React.useState({text: simpleText})
return (
<div>
<Headline text={text}/>
<Headline text={nested.text}/>
<button onClick={()=>setText(simpleText)}>
test 1: the first line should not change (expected)
</button>
<button onClick={()=>setNested({text: simpleText})}>
test 2: the second line will not change (expected)
</button>
<button onClick={()=>setText(new String(simpleText))}>
test 3: the first line will change on every click (why?)
</button>
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#app"))
Here is a live playground in jsfiddle:
https://jsfiddle.net/fL0psxwo/1/
Thank you React folks, cheers!
Update 1:
Thanks Dennis for mentioning why-did-you-render
The author points some very useful articles. I think it could be very educational to everybody.
https://medium.com/welldone-software/why-did-you-render-mr-big-pure-react-component-part-2-common-fixing-scenarios-667bfdec2e0f
Update 2:
I created a new hook called withDirtyCheck so that my code will automatically do content dirty check.
import isEqual from 'lodash-es/isEqual';
export const withDirtyCheck = ([getter, setter]) => {
const setStateIfDirty = (nextState) =>
setter((prevState) => (isEqual(prevState, nextState) ? prevState : nextState));
return [getter, setStateIfDirty];
};
Checkout my latest library https://github.com/stanleyxu2005/react-einfach

The problem is that with new operator you creating a String Object which is always different from the previous state.
'hello world' === new String('hello world') // false, always.
'hello world' === String('hello world') // true
Check this example:
setText(prevState => {
// Will render
// const currState = new String(simpleText);
// Won't render
const currState = String(simpleText);
console.log(prevState === currState); // if true, no re-render
// if false, re-render
return currState;
});
Refer to What is the difference between string primitives and String objects in JavaScript?

Related

React component doesn't re-render on first prop-change

I am new to React and I am trying to build a hangman game.
At the moment I am using a hardcoded list of words that the program can choose from. So far everything worked great, but now I am trying to reset the game and the react component that should rerender upon one click only re-renders after two clicks on the reset button and I don't know why
these are the states that I am using :
function App() {
const [numberInList, setNumberInList] = useState(0)
const randomWordsList = ["comfort", "calm", "relax", "coffee", "cozy"];
const [generatedWord, setGeneratedWord] = useState(
randomWordsList[numberInList]
);
const [generatedWordLetters, setGeneratedWordLetters] = useState(
randomWordsList[numberInList].split("").map((letter) => {
return { letter: letter.toUpperCase(), matched: false };
})
);
function resetGame(){
setNumberInList(prev => prev + 1)
setGeneratedWord(randomWordsList[numberInList])
setGeneratedWordLetters(
generatedWord.split("").map((letter) => {
return { letter: letter.toUpperCase(), matched: false };
})
);
setFalseTries(0)
}
this is the reset function I am using
within teh function every state gets updated correctly apart from the generatedWordLetters state, which only gets updated upon clicking the reset button two times.
I can't seem to solve this problem on my own, so any help is appreciated!
Please check useEffect on React. You can use boolean flag as state, put the useEffect parameters like below
React.useEffect(() => {
// here your code works
},[flag])
flag is your boolean state when it changes on reset function, your component re render

When to optimize renders in react with useMemo and useCallback

I've read a few articles on when to optimize rendering in React, however, I still have some doubts.
const RepairNoticeContainer = () => {
const dispatch = useDispatch();
const history = useHistory();
const { siteType, siteId } = useParams();
const data = useSelector(pageSelectors.getData);
const showRepairNotice = data.grid.cols.lg !== 36;
const handleRepairClick = () => {
dispatch(
pagesActions.copyAndRepairCurrentPage(newPageId => {
history.push(`/editor/${siteType}/${siteId}/pages/${newPageId}`);
})
);
};
return showRepairNotice ? <RepairNotice onRepairClick={handleRepairClick} /> : null;
};
As far as I can understand, it would be beneficial to use useCallbackfor handleRepairClick to avoid rerenders of <RepairNotice/>. But what about showRepairNoticevariable? Should it be wrapped in a useMemo for optimization?
const RepairNotice = ({ onRepairClick }) => {
const translate = useTranslator();
let message = translate("repair_warning");
message = message.charAt(0).toLowerCase() + message.slice(1);
return (
<MessageBox type="warning" icon="information11" className="mb-0 mt-2">
<div className="row">
<div className="col">
<b>{translate("warning")}:</b> {message}
</div>
<div className="col-auto text-right">
<Button color="danger" onClick={onRepairClick} size="small">
{translate("repair_now")}
</Button>
</div>
</div>
</MessageBox>
);
A simillar question for this example. Would it be beneficial to wrap message inside of useMemo?
const Page = ({ status }) => {
const unsavedData = status?.unsavedData ? true : false;
return (
<Fade>
<div className="Page">
<NavConfirmModal active={unsavedData} onSavePage={onSavePage} />
</div>
</Fade>
);
};
Lastly, should useMemo be used unsavedData?
Explanations would be much appreciated.
As far as I can understand, it would be beneficial to use useCallback for handleRepairClick to avoid rerenders of
That's right. while wrapping handleRepairClick you will, simply speak, prevent creating a new instance of this function so it will save RepairNotice nested component from redundant rerenders because it relies on this function in props. Another good case for useMemo is when you're rendering a list of items and each relies on the same handler function declared in their parent.
Very good useCallback explanation here.
But what about showRepairNotice variable? Should it be wrapped in a
useMemo for optimization?
It's just a simple "equation" check which is really cheap from performance side - so there is really no need in useMemo here.
Would it be beneficial to wrap message inside of useMemo?
Yes, it would. Since there are at least 3 actions javascript has to fire upon the message (charAt, toLowerCase, slice) and you don't really want this calculations to fire every time the RepairNotice component gets rerendered.
should useMemo be used unsavedData?
It might be preferable to wrap unsavedData into useMemo if NavConfirmModal will be wrapped in React.Memo or in the case of "heavy calculations". So for the current case - it would not really make a difference. (btw calculating unsavedData could be written just like !!status?.unsavedData to get boolean).
And very good useMemo explanation here.

I have a useReducer state that's an object holding an array of numbers. When I try to increment one of those numbers in the reducer, it goes up by 2?

Here's my react component and my reducer function:
const testReducer = (state) => {
const newState = {...state}
newState.counts[0] += 1
return newState
}
function App() {
const [countState, dispatchCount] = useReducer(testReducer, {counts: [0]})
return (
<div className="App">
<h1>{countState.counts[0]}</h1>
<button onClick={dispatchCount}>up</button>
</div>
);
}
When the button is clicked and the reducer is executed, I expect the count displayed in the H1 to increment by one. This happens when the button is clicked the first time, but every subsequent click increments it by 2.
This happens no matter what the count is initialized to. If the value I'm incrementing is not in an array, it works normally.
Can anyone tell me why this is happening?
Issue
newState.counts[0] = += 1 isn't valid syntax. Assuming you meant newState.counts[0] += 1 then you are mutating the state object.
const testReducer = (state) => {
const newState = {...state}
newState.counts[0] += 1 // <-- mutates newState.counts!!
return newState
}
In all likelihood this mutation is being exposed in your app by being rendered within a React.StrictMode component.
StrictMode - Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- this
Solution
Even though you are shallow copying state you still need to return a new counts array reference.
const testReducer = (state) => {
const newState = {
...state,
counts: [state.counts[0] + 1]
};
return newState;
};

Can't understand "state" in reactjs

In whichever site I visit to get my doubt clear about state in react I always found this defination in common which is:
"an object of a set of observable properties that control the behavior of the component". And I still don't understand the state in react. Consider an example below
import React,{useState} from 'react';
export const City = ()=>{
const [altitude,setAltitude] = useState("");
const getAltitude=()=>{
navigator.geolocation.watchPosition((position)=>{
const alt = {
lat:position.coords.latitude,
long:position.coords.longitude
}
setAltitude(alt);
console.log(altitude);
})
}
return(
<div id="location">
{getAltitude()}
<h3>This is location</h3>
</div>
)
}
But the above program can also be written without using state as shown below
import React,{useState} from 'react';
export const City = ()=>{
let lat;
let long;
const getAltitude=()=>{
navigator.geolocation.watchPosition((position)=>{
lat = position.coords.latitude;
long = position.coords.longitude;
})
console.log(lat,long);
}
return(
<div id="location">
{getAltitude()}
<h3>This is location</h3>
</div>
)
}
If we can write in this way too then what is the use of state in react.
If I'm wrong I request you to explain me in detail. I'm not able to sleep unless this doubt doesn't get clear.
For the understanding purpose I've created these two snippets, one using state variable and the other using regular js variables.
Using state variable
const { useState } = React;
const Counter = () => {
const [count, setCount] = useState(0);
const onClick = () => {
//Update the state
setCount(c => c + 1);
}
return (
<div>
Count: {count}
<div>
<button onClick={onClick}>Increment</button>
</div>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Using regular variable
const { useState } = React;
const Counter = () => {
let count = 0;
const onClick = () => {
//Update the variable
count += 1;
console.log(count);
}
return (
<div>
Count: {count}
<div>
<button onClick={onClick}>Increment</button>
</div>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
In both of the above scenarios we are updating the count on click of the button, but then in scenario 1, the updated value is displayed on the DOM where as in scenario 2, it's not.
So, basically when you want to re-render the component on change of the variable we keep those variables in the state. React will be able to understand the state changes and update the DOM accordingly
As stated correctly in your citation, React maintains a state that is used to to figure out, beside some other features, when to re-render your component. In your examples, it seems like you wanted to add an event listener for watchPosition, and show the calculated values in your City component. If I am correct please consider this example:
import React,{useState} from 'react';
export const City = ()=>{
const [altitude,setAltitude] = useState({});
const calculateAltitude=()=>{
navigator.geolocation.watchPosition((position)=>{
const alt = {
lat:position.coords.latitude,
long:position.coords.longitude
}
setAltitude(alt); // you set the state here
})
}
calculateAltitude(); // This function is called on each render. It's better to put it into a "componentDidMount" equivalent (second example)
return(
<div id="location">
<h3>This is the location</h3>
<div>Lat: {altitude.lat}</div> {/*You use the state here*/}
<div>Lat: {altitude.long}</div>
</div>
)
}
Each time watchPosition is executed, your state altitude is updated and the component will re-render. This means, that the render function is executed and the current state of altitude is used to display the latitude and longitude. In the first example, calculateAltitude() will be executed each time, your component renders. Since that is not best practice, you should put that call into a useEffect hook, with an empty-array dependency. It is an equivalent to the componentDidMount() fucntion for React class components (React.Component). You can find a good explanation here.
So, in order to have a clean code, you should use this component:
import React,{useState, useEffect} from 'react';
export const City = ()=>{
const [altitude,setAltitude] = useState({});
const calculateAltitude=()=>{
navigator.geolocation.watchPosition((position)=>{ // each time this geolocation lib calls your function, your state will be updated.
const alt = {
lat:position.coords.latitude,
long:position.coords.longitude
}
setAltitude(alt); // you set the state here
})
}
useEffect(() =>
{
calculateAltitude() // register your event listener once.
},[]) // on Component mount. Executed once.
return(
<div id="location">
<h3>This is the location</h3>
<div>Lat: {altitude.lat}</div> {/*You use the state here. It is updated each time your geolocation lib calls your listener.*/}
<div>Lat: {altitude.long}</div>
</div>
)
}
I recommend to read carefully about the React states. Here are some good links:
offical doc
article about react states in function
components
One of the features that is at the fundament of React is that it allows you to update your UI, or, in other terms, the HTML output of your app, after a change to a variable. In React, state is a way to declare the variables that, depending on your business logic, can ultimately update the UI. React does this by keeping track of the state and rerendering parts of the HTML that need the update.
In your example, you made altitude part of your state, but your logic does not update the UI anywhere—it just determines a geolocation on first load. If this is what you want, that's fine, you won't need state for that. If, however, you want to make updates to your HTML, e.g. if you'd like to show the realtime location of a user on a map, you could make use of React's state: you write location updates to the state, and will need some custom logic that handles what happens if the altitude value has changed. You could write this custom logic as a side effect.
state variables will be accessible into jsx ( i mean in rendered part of the component), so in your example, altitude will be accessible in the html part, but if you didn't assign it to state variable wont be able to access it
return(
<div id="location">
<h3>This is location {altitude.alt}</h3>
</div>
)

Why is this a valid approach for updating state?

In the React docs, it mentioned that next state shouldn't be directly computed from current state. This is because state updates are asynchronous, so you can't be assured that you are using the correct value of state.
However, in the official tutorial, you will see this function:
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
You can see that the variable history is a function of state.history and state.stepNumber. Isn't this a contradiction of what was mentioned in the docs, or am I missing something?
People get a little too dogmatic about it in my opinion, but there have been enough hard to trace bugs that maybe it's justified. Ultimately, you have to know why its recommended in order to know if its ok not to in special cases.
Why is it recommended?
State updates are asynchronous and can be batched, and you may be using stale values if you update state multiple times and one or more of your updates are based on previous values. In a functional component, you have the same risks do to stale closures.
Example where state should be updated 5 times, but is only incremented once:
Functional component example:
const {useState} = React;
const Example = () => {
const [value, setValue] = useState(0);
const onClick = () => {
[1,2,3,4,5].forEach(() => {
console.log('update');
setValue(value + 1);
});
}
return (
<div>
<button onClick={onClick}>Update 5 times</button>
<div>Count: {value}</div>
</div>
)
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Class component example:
const {useState, useEffect} = React;
class Example extends React.Component {
state = {
count: 0
}
onClick = () => {
[1,2,3,4,5].forEach(() => {
this.setState({count: this.state.count + 1});
});
}
render() {
return (
<div>
<button onClick={this.onClick}>Update 5 times</button>
<div>Count: {this.state.count}</div>
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Is it ok to use normal state updates based on previous state?
It depends on your use case. If you know for an absolute certainty that you will only update state once, then technically it's safe. The problem is, while it may be safe today, tomorrow you or another developer may unknowingly change that...
At the end of the day, I think you have to ask yourself: "Do I gain any extra benefit from not using a functional update?", if so then understand the risks of future bugs and go for it (you should probably document it heavily too). But almost every time, the answer will be just use the functional update.
because this: const history = this.state.history.slice(0, this.state.stepNumber + 1); isn't actually mutating any state. it's just assigning it to a const for the local function to use. it's not actually manipulating the state itself
a few lines below it uses this.setState({}) to directly change state in the standard way
the assigning to a const is different than if you just did this: this.state.history.slice(0, this.state.stepNumber + 1) which would be directly manipulating it

Categories