setTimeout callback called twice in React functional component? - javascript

Consider the following code:
import React from "react";
function App() {
console.log("render");
setTimeout(() => {
console.log("time is up");
}, 2000);
return <div>nothing to see here</div>;
}
export default App;
I expected the following output:
render
time is up
But the real output in the Chrome console is:
Note the 2 before time is up, showing us that time is up was output twice.
I don't understand why time is up is output twice. Can anyone explain this?

The component is rendered twice because CRA sets React's strict mode by default, which among other things tries to help you detect side effects (emphasis mine):
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
So far, this is covered by posts like:
Why my simple react component print console twice?
Why is console.log logging twice in react js?
Why does useState cause the component to render twice on each update?
However, you might then expect both "render" and "time is up" to be logged twice. The reason that doesn't happen, which as far as I can find hasn't been covered yet on SO, is that React was updated to deliberately suppress the duplicate logs:
This disables console.log by temporarily patching the global console
object during the second render pass when we double render in strict
mode in DEV.
This applies only to console.logs directly in the functions mentioned above, which doesn't include setTimeout callbacks, so the second console.log("render") is swallowed but the second console.log("time is up") is not.

I believe the problem here is that you didn't put the setTimeout inside a useEffect. This means that when the app rerenders another timeout will get started and cause the issue you are finding. Try something like this.
import React, {useEffect} from "react";
function App() {
console.log("render");
useEffect(() => {
const timer = setTimeout(() => {
console.log('time is up');
}, 2000);
return () => {
clearTimeout(timer);
}
}, []);
return <div>nothing to see here</div>;
}
export default App;

React.StrictMode is a feature intended to ease devs spot problems related to React Lifecycle
It only happens when you run in development mode and renders with extra rules like rendering components more than once.
I think the documentation does a great job explaining this. StrictMode: React
Understandably, it gets irritating for people notice for the first time!

Related

How to tell if a React functional component has been rendered in code?

I am trying to add some logging to see how much time it takes to render a functional component in React. The tricky part to me is how to actually tell when the component has been rendered. By "rendered", it means the user can see it in the front end (all parts in that component should be displayed). I am trying to use useEffect to achieve this, as it can tell when the component is mounted.
The code is something like below:
function showComponent() {
console.log('start rendering....start timer');
console.log(new Date().getTime());
setStoreViewState('foobar');
}
observer(function Component() {
React.useEffect(() => {
console.log('rendering complete....stop timer');
console.log(new Date().getTime());
}, []);
......
const thingsToShow = getStoreViewState();
return (<div>{thingsToShow}</div>)
}
I am wondering if this is an accurate way to achieve this. Thanks!
According to the docs, the useEffect with no dependency means that it has finished rendering.
https://reactjs.org/docs/hooks-effect.html
Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.
The quote above is from the docs.

Why does setState Hook from React is rendering twice even not changing the state?

I was studying the React hooks and inserting some console logs in the code to better understand the render flow. Then I started to simulate the setState effects sending the same value to see if React would render it again.
import { useState } from "react";
function ManComponent() {
/* States */
const [shirt, setShirt] = useState("Blue Shirt");
console.log("Rendering man with "+shirt);
/* Actions */
const changeShirt = (newShirt) => {
console.log("[Man] Changing shirt from "+shirt+" to "+newShirt);
setShirt(newShirt);
};
return (
<div>
<p>The man is using: {shirt}</p>
<div>
<button onClick={() => changeShirt("Red Shirt")}>Change to red shirt</button>
<button onClick={() => changeShirt("Blue Shirt")}>Change to blue shirt</button>
</div>
</div>
);
}
export default function App() {
console.log("Rendering app");
return (
<ManComponent />
);
}
If I click at "Change to red shirt" three times, I get two logs saying that the component is rendering. It should be just one, since I changed the value just once, right?
My problem is to understand why does the component renders two times if the state just changed once.
P.S: I tried to remove the Strict Mode, and it had no effect. I'm using React 17 version, by the way.
What's happening
The React documentation addresses exactly this behavior in the useState hook API.
Bailing out of a state update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree.
It's not Strict mode
Strict mode was a good guess, since it used to trigger duplicate logs in the render cycle before React 17, though it was updated to silence these duplicate logs to avoid confusion.
Starting with React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions. However, it may cause undesired behavior in certain cases where a workaround can be used.
...which caused additional confusion, leading to another update on that strict mode logging behavior (which is not released yet I believe).
The workaround mentioned in the quote above to debug strict mode rendering with logs is to keep a module scoped copy of the log function.
const log = console.log;
const App = () => {
log('Will not be silenced by React');
return //...
};

Component rerenders when state has not changed on the second click

I have a tabs component that changes state every time a different tab is clicked.
Component Parent
import { useState } from "react";
import "./styles.scss";
import MemoizedTab from "./tab";
export default function App() {
const [selectTab, setSelectTab] = useState("a");
console.log("parent render");
return (
<div className="App">
<div className="tab-list">
<MemoizedTab
tab={"a"}
title={"First Title"}
setSelectTab={setSelectTab}
/>
<MemoizedTab
tab={"b"}
title={"Second Title"}
setSelectTab={setSelectTab}
/>
<MemoizedTab
tab={"c"}
title={"Third Title"}
setSelectTab={setSelectTab}
/>
</div>
{selectTab === "a" && <div>this is a</div>}
{selectTab === "b" && <div>this is b</div>}
{selectTab === "c" && <div>this is c</div>}
</div>
);
}
Component Child
import { memo } from "react";
const MemoizedTab = memo(({ title, tab, setSelectTab }) => {
console.log("child render");
const handleClick = (tab) => {
setSelectTab(tab);
};
return <p onClick={() => handleClick(tab)}>{title}</p>;
});
export default MemoizedTab;
After the initial render the parent has a state of "a". When I click on "First Title" which sets the state to "a" again, nothing renders as expected.
When I first click on "Second Title" which sets the state to "b", I get a console log of "parent renders", which is as expected. But when I click on "Second Title" for the second time, I get a console log of "parent renders" again even though the state didn't change. Nothing logs on the third or fourth clicks. It's always the second.
This same behavior happens when I click on "Third Title" which sets the state to "c".
Why does my parent re-render on the second click after state transitions?
Codesandbox Link
React is not actually re-rendering your component. You can verify this by moving the console.log inside the componentDidUpdate hook.
useEffect(()=>{
console.log('Parent re-rendered!')
})
This console.log won't get logged by the second time you click on any of the tabs.
While the console.log in your provided example does get printed, React eventually bails out of the state update. This is actually an expected behaviour in React. The following extract is from the docs:
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again
before bailing out. That shouldn’t be a concern because React won’t
unnecessarily go “deeper” into the tree.
This is an interesting one - I did some digging and found the following related React issues:
Hooks: Calling setState with the SAME value multiple times, evaluates the component function up-to 2 times
This is the same issue that you are describing, from a comment there by Sebastian Markbåge it appears React requires the additional render to confirm that the two versions are the same:
This is a known quirk due to the implementation details of concurrency in React. We don't cheaply know which of two versions is currently committed. When this ambiguity happens we have to over render once and after that we know that both versions are the same and it doesn't matter.
useState not bailing out when state does not change
This describes the same underlying concept - setting state to the same value may result in the functional component to running again. The key takeaway from them is the last comment from Dan Abramov on the second issue:
React does not offer a strong guarantee about when it invokes render functions. Render functions are assumed to be pure and there should be absolutely no difference for correctness regardless of how many times they're called. If calling it an extra time causes a bug, it's a problem with the code that needs to be fixed.
From the performance point of view, the cost of re-running a single function is usually negligible. If it's not, you should add some useMemos to the parts that are expensive. React does guarantee that if a bailout happens, it will not go updating child components. Even though it might in some cases re-run the component function itself.
So React doesn't gaurntee when it will invoke the functional component, and in this case it appears to run it the additional time checking that the result is the same.
The good news is that although it runs the function an additional time, from what I can tell it doesn't appear to actually rerender on the second invocation - if you use the React profiler, looking at the Flamegraph you can see the first time App renders due to the hook change:
but on the second click, App does not actually rerender:

React - error in useEffect(). does dat make everything on page go in infinite loop?

I've converted all my class components to functional components. Now, when I change some code in the IDE during development my page goes in an infinite loop. I'm almost certain one on my useEffect() hooks has an mistake (or missing) conditional firing variable. But it would take some time to figure out which one.
So, my question is. Is there an easy way to figure out which one is broken that would cause the loop. And would it also loop after build, or only in development?
As requested a code example. It is very basic since I have about 20 using this principle.
import React, {useEffect} from "react";
const Layout = ({ data }) => {
useEffect(() => {
// some jQuery stuff
}, [data.myConditionalVar])
return (
<div>
// my stuff
</div>
)
}
useEffect gets called every time the second argument you pass is changed. In your case it is data.myConditionalVar.
So, I am assuming inside useEffect you are updating the data using jquery which forces React to call the useEffect again resulting in a never-ending loop.

React functional component reinitialises local functions and variables on re render (React Hooks)

So I have started using React hooks now. I have been experimenting with the API for some time now. i really like the idea of bringing the state to functional components. but there is this one thing which keeps on bothering me and it doesn't feel right in the gut when i am trying to use it. I tried posting on RFCs but it's too crowded there now. everything seems lost there.
Here is a piece of code from my example.
import React, { useState } from "react";
function Counter() {
const [counterState,incrementCounterState] = useCommontState(0);
function doSomething (){
// does something and then calls incrementCounterState
// with the updated state.
}
return (
<div>
<p>{counterState}</p>
<button onClick={incrementCounterState}>increase</button>
....
.... // some jsx calling local scoped functions.
....
</div>
);
}
function useCommontState(defaultValue){
var [state, setState] = useState(0);
function increment(){
setState(defaultValue+=1);
}
return [state, increment]
}
export default Counter;
I can easily take out state and setState methods out and create a custom hook but my problem is with the local functions that are used by the component. since state is now part of the component there will be cases where some logic will decide what to do next with the state.
Also, when the component re-renders on state change everything gets reinitialized. which is my problem. I know that useState has its own way of handling the issue. but my problem is with my own functions. the click handlers. on change events, callbacks for child components etc. all that will be reinitialized everytime the component renders. this doesn't feel right to me.
Are there any ways by which we can work around it. it's a new API. we are not even sure if it will make into react 17. but has anyone come across any better way to do it?
I had the same concerns as well when I first saw the proposal, but this was addressed in the React Docs Hooks Proposal FAQ:
Are Hooks slow because of creating functions in render?
No. In modern browsers, the raw performance of closures compared to classes doesn’t differ significantly except in extreme scenarios.
My takeaway is that although you have additional overhead now in the repeated declarations per render, you have additional wins elsewhere:
Hooks avoid a lot of the overhead that classes require, like the cost of creating class instances and binding event handlers in the constructor.
Idiomatic code using Hooks doesn’t need the deep component tree nesting that is prevalent in codebases that use higher-order components, render props, and context. With smaller component trees, React has less work to do.
Overall the benefits might be more than the downsides which makes hooks worth using.
You can always simplify the code to take functions out so that they aren't initialised always, by passing the required values as constants.
import React, { useState } from "react";
function doSomething (counterState, incrementCounterState){
// does something and then calls incrementCounterState
// with the updated state.
}
function Counter() {
const [counterState,incrementCounterState] = useCommontState(0);
return (
<div>
<p>{counterState}</p>
<button onClick={incrementCounterState}>increase</button>
....
.... // some jsx calling local scoped functions.
....
</div>
);
}
function increment(defaultValue, setState){
setState(defaultValue + 1);
}
function useCommontState(defaultValue){
var [state, setState] = useState(0);
return [state, increment]
}
export default Counter;
Also in my opinion the function design being suggested in all the demos and docs is for people to get comfortable with it and then think about the re-initialization aspects. Also the cost that re-initialization would significanly be overpowered by the other benefits that it provides.
I'm using createOnce helper function to prevent reinitialises, But I'm not sure if it's correct or not.
utils/createOnce.js
import { useMemo } from 'react';
export const createOnce = toCreate => useMemo(() => toCreate, []);
SomeComponent.js
...
const someFunction = createOnce((counter) => {
// whatever
return counter + 1;
});
...

Categories

Resources