I am using Material-UI ClickAwayListener
and react-router
for my app. The bug I encountered is that when the callback for ClickAwayListener
get executed, it is stopped mid-way for a useEffect
to run and after that it resume running. This behavior is not expected from a callback. The callback should be fully executed before the useEffect
can run. Below are the code I create to demonstrate the problem and this is the demo for the code
import React, { useEffect, useState } from "react";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useHistory,
useParams
} from "react-router-dom";
export default function BasicExample() {
return (
<Router>
<div>
<Switch>
<Route exact path="/">
<ButtonPage />
</Route>
{/*Main focus route here*/ }
<Route path="/:routeId">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
// Main focus here
function Home() {
const history = useHistory();
const { routeId } = useParams();
const [count, setCount] = useState(0);
const handleClick1 = () => {
history.push("/route1");
};
const handleClick2 = () => {
history.push("/route2");
};
// useEffect run on re-render and re-mount
useEffect(() => {
console.log("Re-render or remount");
});
useEffect(() => {
console.log("Run on route change from inside useEffect");
}, [routeId]);
const handleClickAway = () => {
console.log("First line in handle click away");
setCount(count + 1);
console.log("Second line in handle click away");
};
return (
<div>
<button onClick={handleClick1}>Route 1 </button>
<button onClick={handleClick2}>Route 2 </button>
<ClickAwayListener onClickAway={handleClickAway}>
<div style=>
Hello here
</div>
</ClickAwayListener>
</div>
);
}
// Just a component such that home route can navigate
// Not important for question
function ButtonPage() {
const history = useHistory();
const handleClick1 = () => {
history.push("/route1");
};
const handleClick2 = () => {
history.push("/route2");
};
return (
<div>
<button onClick={handleClick1}>Route 1 </button>
<button onClick={handleClick2}>Route 2 </button>
</div>
);
}
In specific, when I click outside the ClickAwayListener
the handleClickAway
run normally and the logging message is
First line in handle click away
Second line in handle click away
Re-render or remount
Until I choose to click on the button
that navigate to other route. Here thing get weird: handleClickAway
run the first logging line, then the useEffect
run and print its logging, then handleClickAway
resume and print its second line. So if I do so this is the logging
First line in handle click away
Re-render or remount
Run on route change from inside useEffect
Second line in handle click away
After doing some test on this bug I figured out that the thing that cause this bug is the setCount
inside the handleClickAway
. If I remove this line the function handleClickAway
will run as expected for all cases. My conclusion is that, changing component state, or should I say, perform any actions that cause component to re-render inside handleClickAway
in combination with route navigation can cause this bug.
This behavior is strange, because as far as I know, there is no way for a normal, non-promise related callback to stop mid-way like this. I guess the ClickAwayListener
somehow make handleClickAway
into Promise or something. But even then, there is no reason for it to stop at the setCount
and let useEffect
run? Can someone explain this to me?
from Callback function for ClickAwayListener stop midway on execution
No comments:
Post a Comment