I have a Tabs
component that has bunch of NavLink
s. I'm trying to show a loading spinner based on the NavLink
's isPending
property on the parent component. So I'm showing spinner when there is a navigation in the NavLink
of Tabs
component. I'm only triggering the showing spinner from the NavLink
component. I'm handling turning the spinner off from the parent using navigation
.
The parent component is something like:
import { useNavigation} from "@remix-run/react";
const [isTabNavigating, setIsTabNavigating] = useState(false);
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === "idle") setIsTabNavigating(false);
}, [navigation.state]
);
return (
<>
<Tabs setIsTabNavigating={setIsTabNavigating} />
{ isTabNavigating ? <Spinner /> : <Outlet /> }
</>
);
And the tabs component:
<>
{tabItems.map((tab) => (
<div>
<NavLink to={tab.link}>
{
({ isPending }) => {
if (isPending) setIsTabNavigating(true);
return <span>{tab.title}</span>
}
}
</NavLink>
</div>
}
</>
I'm getting this warning:
Warning: Cannot update a component while rendering a different component (NavLink). To locate the bad setState() call inside NavLink, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
The code is enqueueing a state update during the component render, as an unintentional side-effect.
You can abstract the children render prop into a React component such that the isTabNavigating
state update can be properly enqueued in a lifecycle method, e.g. via a useEffect
hook.
Example:
const TabItem = ({
isPending,
isTabNavigating,
setIsTabNavigating,
tab
}) => {
useEffect(() => {
// If not currently tab navigating and isPending is true,
// then update to start tab navigating.
if (!isTabNavigating && isPending) {
setIsTabNavigating(true);
}
}, [isPending, isTabNavigating, setIsTabNavigating]);
return <span>{tab.title}</span>;
};
tabItems.map((tab) => (
<div key={tab.link}>
<NavLink to={tab.link}>
{({ isPending }) => (
<TabItem
isPending={isPending}
isTabNavigating={isTabNavigating}
setIsTabNavigating={setIsTabNavigating}
tab={tab}
/>
)}
</NavLink>
</div>
}
Pass both isTabNavigating
state and setIsTabNavigating
callback down as props so they are accessible in the sub-ReactTree.
const [isTabNavigating, setIsTabNavigating] = useState(false);
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === "idle"){
setIsTabNavigating(false);
}
}, [navigation.state]);
return (
<>
<Tabs
isTabNavigating={isTabNavigating}
setIsTabNavigating={setIsTabNavigating}
/>
{isTabNavigating ? <Spinner /> : <Outlet />}
</>
);