I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through Firestore. Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points. The second time it renders all the data is displayed correctly. Can someone guide me to the solution.
class Update extends Component {
constructor(props) {
super(props);
const { match } = this.props;
this.state = {
phoneNumber: match.params.phoneNumber,
points: 0,
error: ''
}
}
getPoints = () => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
docRef.get().then((doc) => {
if (doc.exists) {
const points = doc.data().points;
this.setState(() => ({ points }));
console.log(points);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
const error = 'This phone number is not registered yet...'
this.setState(() => ({ error }));
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
} else {
history.push('/')
}
});
}
componentDidMount() {
if(this.state.phoneNumber) {
this.getPoints();
} else {
return null;
}
}
render() {
return (
<div>
<div>
<p>{this.state.phoneNumber} has {this.state.points} points...</p>
<p>Would you like to redeem or add points?</p>
</div>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
);
}
}
export default Update;
You are running your app in strict mode. Go to index.js and comment strict mode tag. You will find a single render.
This happens is an intentional feature of the React.StrictMode. It only happens in development mode and should help to find accidental side effects in the render phase.
From the docs:
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:...
^ In this case the render function.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering.
Just add another Boolean key like isFetching, set it to true when you call the server and set it to false when the data is received.
Your render can look something like this:
render() {
const { isFetching } = this.state;
return (
<div>
{isFetching ? (
<div>Loading...</div>
) : (
<div>
<p>
{this.state.phoneNumber} has {this.state.points} points...
</p>
<p>Would you like to redeem or add points?</p>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
)}
</div>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)
All these methods are called more than once, so it is important to avoid having side-effects in them. If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.
React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions.
These functions are:
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 behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
I worked around this by providing a custom hook. Put the hook below into your code, then:
// instead of this:
useEffect( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
}, []);
// do this:
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Here is the code for the hook:
export const useEffectOnce = ( effect => {
const destroyFunc = useRef();
const calledOnce = useRef(false);
const renderAfterCalled = useRef(false);
if (calledOnce.current) {
renderAfterCalled.current = true;
}
useEffect( () => {
if (calledOnce.current) {
return;
}
calledOnce.current = true;
destroyFunc.current = effect();
return ()=> {
if (!renderAfterCalled.current) {
return;
}
if (destroyFunc.current) {
destroyFunc.current();
}
};
}, []);
};
See this blog for the explanation.
Well, I have created a workaround hook for this. Check this, if it helps:
import { useEffect } from "react";
const useDevEffect = (cb, deps) => {
let ran = false;
useEffect(() => {
if (ran) return;
cb();
return () => (ran = true);
}, deps);
};
const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";
export const useOnceEffect = isDev ? useDevEffect : useEffect;
CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix
React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. Let the re-renders to be managed by react. Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.
I am new to reactJS and am writing code so that before the data is loaded from DB, it will show loading message, and then after it is loaded, render components with the loaded data. To do this, I am using both useState hook and useEffect hook. Here is the code:
The problem is, useEffect is triggered twice when I check with console.log. The code is thus querying the same data twice, which should be avoided.
Below is the code that I wrote:
import React from 'react';
import './App.css';
import {useState,useEffect} from 'react';
import Postspreview from '../components/Postspreview'
const indexarray=[]; //The array to which the fetched data will be pushed
function Home() {
const [isLoading,setLoad]=useState(true);
useEffect(()=>{
/*
Query logic to query from DB and push to indexarray
*/
setLoad(false); // To indicate that the loading is complete
})
},[]);
if (isLoading===true){
console.log("Loading");
return <div>This is loading...</div>
}
else {
console.log("Loaded!"); //This is actually logged twice.
return (
<div>
<div className="posts_preview_columns">
{indexarray.map(indexarray=>
<Postspreview
username={indexarray.username}
idThumbnail={indexarray.profile_thumbnail}
nickname={indexarray.nickname}
postThumbnail={indexarray.photolink}
/>
)}
</div>
</div>
);
}
}
export default Home;
Can someone help me out in understanding why it is called twice, and how to fix the code properly?
Thank you very much!
Put the console.log inside the useEffect
Probably you have other side effects that cause the component to rerender but the useEffect itself will only be called once. You can see this for sure with the following code.
useEffect(()=>{
/*
Query logic
*/
console.log('i fire once');
},[]);
If the log "i fire once" is triggered more than once it means your issue is
one of 3 things.
This component appears more than once in your page
This one should be obvious, your component is in the page a couple of times and each one will mount and run the useEffect
Something higher up the tree is unmounting and remounting
The component is being forced to unmount and remount on its initial render. This could be something like a "key" change happening higher up the tree. you need to go up each level with this useEffect until it renders only once. then you should be able to find the cause or the remount.
React.Strict mode is on
StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).
This answer was pointed out by #johnhendirx and written by #rangfu, see link and give him some love if this was your problem. If you're having issues because of this it usually means you're not using useEffect for its intended purpose. There's some great information about this in the beta docs you can read that here
Remove <React.StrictMode> from index.js
This code will be
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
this
root.render(
<App />
);
React StrictMode renders components twice on dev server
You are most likely checking the issue on a dev environment with strict mode enabled.
To validate this is the case, search for <React.StrictMode> tag and remove it, or build for production. The double render issue should be gone.
From React official documentation
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:
Functions passed to useState, useMemo, or useReducer
[...]
Strict Mode - Reactjs docs
Similar question here My React Component is rendering twice because of Strict Mode
Please check your index.js
<React.StrictMode>
<App />
</React.StrictMode>
Remove the <React.StrictMode> wrapper
you should now fire once
root.render(
<App />
);
react root > index.js > remove <React.StrictMode> wrapper
It is the feature of ReactJS while we use React.StrictMode. StrictMode activates additional checks and warnings for its descendants nodes. Because app should not crash in case of any bad practice in code. We can say StrictMode is a safety check to verify the component twice to detect an error.
You will get this <React.StricyMode> at root of the component.
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
if you want to restrict components to render twice, You can remove <React.StrictMode> and check it. But It is necessary to use StrictMode to detect a run time error in case of bad code practice.
if you are using Next js, change reactStrictMode from "true" to false :
add this to your next.config.js
reactStrictMode: false,
I have found a very good explanation behind twice component mounting in React 18. UseEffect called twice in React
Note: In production, it works fine. Under strict mode in the development environment, twice mounting is intentionally added to handle the errors and required cleanups.
I'm using this as my alternative useFocusEffect. I used nested react navigation stacks like tabs and drawers and refactoring using useEffect doesn't work on me as expected.
import React, { useEffect, useState } from 'react'
import { useFocusEffect } from '#react-navigation/native'
const app = () = {
const [isloaded, setLoaded] = useState(false)
useFocusEffect(() => {
if (!isloaded) {
console.log('This should called once')
setLoaded(true)
}
return () => {}
}, [])
}
Also, there's an instance that you navigating twice on the screen.
Not sure why you won't put the result in state, here is an example that calls the effect once so you must have done something in code not posted that makes it render again:
const App = () => {
const [isLoading, setLoad] = React.useState(true)
const [data, setData] = React.useState([])
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
setLoad(false)//causes re render
setData(data)//causes re render
})
},[])
//first log in console, effect happens after render
console.log('rendering:', data.length, isLoading)
return <pre>{JSON.stringify(data, undefined, 2)}</pre>
}
//render app
ReactDOM.render(<App />, 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>
To prevent the extra render you can combine data and loading in one state:
const useIsMounted = () => {
const isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};
const App = () => {
const [result, setResult] = React.useState({
loading: true,
data: []
})
const isMounted = useIsMounted();
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
//before setting state in async function you should
// alsways check if the component is still mounted or
// react will spit out warnings
isMounted.current && setResult({ loading: false, data })
})
},[isMounted])
console.log(
'rendering:',
result.data.length,
result.loading
)
return (
<pre>{JSON.stringify(result.data, undefined, 2)}</pre>
)
}
//render app
ReactDOM.render(<App />, 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>
The new React docs (currently in beta) have a section describing precisely this behavior:
How to handle the Effect firing twice in development
From the docs:
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
So this warning should make you double check your useEffect, usually means you need to implement a cleanup function.
This may not be the ideal solution. But I used a workaround.
var ranonce = false;
useEffect(() => {
if (!ranonce) {
//Run you code
ranonce = true
}
}, [])
Even though useEffect runs twice code that matters only run once.
As others have already pointed out, this happens most likely due to a Strict Mode feature introduced in React 18.0.
I wrote a blog post that explains why this is happening and what you can do to work around it.
But if you just want to see the code, here you go:
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
// My actual effect logic...
...
}
}, [])
Or as a re-usable hook:
import type { DependencyList, EffectCallback } from "react"
import { useEffect } from "react"
export function useEffectUnsafe(effect: EffectCallback, deps: DependencyList) {
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
effect()
}
}, deps)
}
Please keep in mind that you should only resort to this solution if you absolutely have to!
I've had this issue where something like:
const [onChainNFTs, setOnChainNFTs] = useState([]);
would trigger this useEffect twice:
useEffect(() => {
console.log('do something as initial state of onChainNFTs changed'); // triggered 2 times
}, [onChainNFTs]);
I confirmed that the component MOUNTED ONLY ONCE and setOnChainNFTs was NOT called more than once - so this was not the issue.
I fixed it by converting the initial state of onChainNFTs to null and doing a null check.
e.g.
const [onChainNFTs, setOnChainNFTs] = useState(null);
useEffect(() => {
if (onChainNFTs !== null) {
console.log('do something as initial state of onChainNFTs changed'); // triggered 1 time!
}
}, [onChainNFTs]);
Here is the custom hook for your purpose. It might help in your case.
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
*
* #param effect
* #param dependencies
* #description Hook to prevent running the useEffect on the first render
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependancies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void) = () => {};
/**
* Updating the ref to false on the first render, causing
* subsequent render to execute the effect
*
*/
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
/**
* Preserving and allowing the Destructor returned by the effect
* to execute on component unmount and perform cleanup if
* required.
*
*/
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependancies);
}
There is nothing to worry about. When you are running React in development mode. It will sometimes run twice. Test it in prod environment and your useEffect will only run once. Stop Worrying!!
It is strict mode in my case. Remove strict mode component at index.tsx or index.jsx
If someone comes here using NextJS 13, in order to remove the Strict mode you need to add the following on the next.config.js file:
const nextConfig = {
reactStrictMode: false
}
module.exports = nextConfig
When I created the project it used "Strict mode" by default that's why I must set it explicitly.
Ok this is maybe a bit late to comment on this - but I found a rather useful solution which is 100% react.
In my case I have a token which I'm using to make a POST request which logs out my current user.
I'm using a reducer with state like this:
export const INITIAL_STATE = {
token: null
}
export const logoutReducer = (state, action) => {
switch (action.type) {
case ACTION_SET_TOKEN :
state = {
...state,
[action.name] : action.value
};
return state;
default:
throw new Error(`Invalid action: ${action}`);
}
}
export const ACTION_SET_TOKEN = 0x1;
Then in my component I'm checking the state like this:
import {useEffect, useReducer} from 'react';
import {INITIAL_STATE, ACTION_SET_TOKEN, logoutReducer} from "../reducers/logoutReducer";
const Logout = () => {
const router = useRouter();
const [state, dispatch] = useReducer(logoutReducer, INITIAL_STATE);
useEffect(() => {
if (!state.token) {
let token = 'x' // .... get your token here, i'm using some event to get my token
dispatch(
{
type : ACTION_SET_TOKEN,
name : 'token',
value : token
}
);
} else {
// make your POST request here
}
}
The design is actually nice - you have the opportunity to discard your token from storage after the POST request, make sure the POST succeeds before anything. For async stuff you can use the form :
POST().then(async() => {}).catch(async() => {}).finally(async() => {})
all running inside useEffect - works 100% and within I think what the REACT developers had in mind - this pointed out that I actually had more cleanup to do (like removing my tokens from storage etc) before everything was working but now I can navigate to and from my logout page without anything weird happening.
My two cents...
I used CodeSandbox and removing prevented the issue.
CodeSandbox_sample
I'm looking for the simplest solution to this problem using the popular React Testing Library. I've looked at a lot of links out there and nothing seems to fit..
Here is the component in it's simplest form below:
import React, { useState, useEffect } from "react";
function App() {
const [loggedIn, setLoggedIn] = useState(false)
useEffect(() => {
setTimeout(() => setLoggedIn(true), 1000)
}, [])
return (
<button data-testid="login-button">{loggedIn ? "Log Out" : "Log In"}</button>
);
}
export default App;
And below is the test for the 'no wait' state of the button but would like to know how to implement 'waitFor' within the same test for the change in text in the button after 1 second..
import { render, screen, waitFor } from "#testing-library/react";
import App from "./App";
test('Check the text "Log out" is eventually there', () => {
render(<App />);
const LoginButton = screen.getByText(/Log/)
expect(LoginButton).toHaveTextContent(/Log In/i);
/*How to use waitFor here*/
});
I know we can mock the return from an API call easily and for this problem we probably have to return a promise before we can test using 'waitFor'. How can we use 'WaitFor' in the example test file above?
You can use jest's Timer Mocks to handle the setTimeouts.
import { act, render, screen } from "#testing-library/react";
import App from "./App";
test("renders learn react link", () => {
// use the fake timer
jest.useFakeTimers();
render(<App />);
// assert the initial text of the button
expect(screen.getByRole("button", { name: "Log In" })).toBeInTheDocument();
// advance the timer
act(() => jest.advanceTimersByTime(1000));
// assert the changed text of the button
expect(screen.getByRole("button", { name: "Log Out" })).toBeInTheDocument();
// switch back to use real timers
jest.useRealTimers();
});
Reference:
Using act() for timers
Thankyou Shyam for helping me discover the solution, which was not
using 'waitFor' from the testing-library in this case:
test('Check the text "Log out" is eventually there', () => {
jest.useFakeTimers();
render();
//Reference the element by locating using Regex
const LoginButton = screen.getByText(/Log/);
//Search in real time for the initial/default text on the button
expect(LoginButton).toHaveTextContent(/Log In/i);
// advance the timer
act(() => jest.advanceTimersByTime(1000));
// assert the changed text of the button
expect(LoginButton).toHaveTextContent(/Log Out/i);
// switch back to use real timers
jest.useRealTimers();
});
hoping someone can help lead me in the right direction with this or point to me to any docs/learnings that might be helpful.
I have two React components:
<Widget/>
<Modal/>
When <Widget/> mounted, <Modal/> is hidden (and vice-versa) using conditional rendering and the same boolean in my ShowModal state value (this boolean gets switched by a button). I made a simplified CodeSandbox for this example here
Where I am getting stuck is I have an async function takeScreenShot() which needs to run after <Widget/> is unmounted completely and before <Modal/> is mounted. Neither <Widget/> (just unmounted) nor <Modal/> (about to mount) should be visible in the DOM when takeScreenShot() is called. I need to make sure this is the case because I am taking a screenshot of the underlying page where I do not want to include either of the components in this. In my CodeSandbox example the output of my screenshot function would render a the gray background without showing the blue or red box
What I have tried
I have tried running a cleanup function in the useEffect hook in the <Widget/> component like so.
useEffect(() => {
return () => takeScreenShot();
}, []);
However it doesn't work because the cleanup function, similar to componentWillUnmount() runs right as the component is about to unmount, not fully unmounted from the DOM. This causes my screenshot to capture the un-mounting component in the image. Does anyone have an ideas to point me in the right direction on this?
Move the code from your App into the Modal so that it gets called when the Modal Loads for the first time. I removed all references to useEffect or useLayoutEffect, although the useLayoutEffect may still be required in the modal.
Code Sample Here ...
You could try running it in a useLayoutEffect hook like ...
useLayoutEffect(() => {
if (!showModal) takeScreenshot();
}, [showModal]);
See ...
https://reactjs.org/docs/hooks-reference.html#uselayouteffect
According to the Docs ...
The signature is identical to useEffect, but it fires synchronously
after all DOM mutations. Use this to read layout from the DOM and
synchronously re-render. Updates scheduled inside useLayoutEffect will
be flushed synchronously, before the browser has a chance to paint.
You will probably need to keep a separate state of whether or not the modal has been loaded to ensure that the effect hook doesn't execute on initial load of your component.
Code Sample Here ...
You could have another state bool that indicates if a screenshot is being taken, if thats true render neither of the two elements.
so something like this:
() => this.setState({screenshot: true}, () => this.takeScreenshot())
then within your takeScreenshot function, after you're done with its execution it sets it back to false
takeScreenshot = () => {
// ...
// ...
this.setState({screenshot: false})
}
now while rendering
{!screenshot && (showModal ? <Modal /> : <Widget />)}
Here's how you can (and should) do it.
async function takeScreenshot() {
console.log("Taking screenshot...");
return new Promise(resolve => {
window.setTimeout(() => {
resolve();
console.log("Screenshot captured!");
}, 3000);
});
}
function Widget({ onUnmount }) {
React.useEffect(() => {
return () => onUnmount();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <p>Widget</p>;
}
function Modal() {
return <p>Modal</p>;
}
function App() {
const [open, setOpen] = React.useState('widget');
const handleUnmount = async () => {
await takeScreenshot();
setOpen('modal');
}
return (
<React.Fragment>
{open === 'widget' && <Widget onUnmount={handleUnmount} />}
{open === 'modal' && <Modal />}
<button
onClick={() => {
if (open !== "widget") return;
setOpen("");
}}
>
Show modal
</button>
</React.Fragment>
)
}
You can run the demo here.
When I transition to this screen, it will do some API calls to fetch the latest data. But it seems not trigger the didFocus event to fire the api calls when I transition from another navigation stack with hooks version while it works well with class version.
How do I make hooks version have the same behavior as class version?
What's the difference between this two version?
class component version
class someScreen extends Component {
componentDidMount() {
const {
navigation,
} = this.props;
this.navFocusListener = navigation.addListener('didFocus', () => {
// do some API calls here
console.log("class version");
API_CALL();
});
}
componentWillUnmount() {
this.navFocusListener.remove();
}
}
console output
transition from other navigation stack to this screen: class version
transition between screens in same stack: class version
Hooks version
const someScreen = ({
navigation,
}) => {
useEffect(() => {
const navFocusListener = navigation.addListener('didFocus', () => {
// do some API calls here
API_CALL();
console.log('hooooks');
});
return () => {
navFocusListener.remove();
};
}, []);
}
console output
transition from other navigation stack to this screen: nothing is shown in console
transition between screens in same stack: hooooks
BTW, here is the workaround solution I found
const someScreen = ({
navigation,
}) => {
useEffect(() => {
const isFocused = navigation.isFocused();
// manually judge if the screen is focused
// if did, fire api call
if (isFocused) {
// do the same API calls here
API_CALL();
console.log('focused section');
}
const navFocusListener = navigation.addListener('didFocus', () => {
// do some API calls here
API_CALL();
console.log('listener section');
});
return () => {
navFocusListener.remove();
};
}, []);
}
console output
transition from other navigation stack to this screen: focused section
transition between screens in same stack: listener section
I guess I found the root cause of the inconsistent behavior.
There is another hook called useLayoutEffect
useLayoutEffect
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
the useLayoutEffect will block the painting while the useEffect will not.
That confirm and explains my guess that the didFocus event had fired, but it didn't trigger the listener since it miss the timing
so in my case, I have to use useLayoutEffect instead of useEffect
reference: https://kentcdodds.com/blog/useeffect-vs-uselayouteffect
https://reactjs.org/docs/hooks-reference.html#uselayouteffect