Reputation: 19680
i seem to be getting the following error when using useEffect
hook.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I believe it has something to do with the async function i am calling to set if the user is authenticated or not.
ProtectedRoute.tsx
export function ProtectedRoute({ ...routeProps }: ProtectedRouteProps): ReactElement | null {
const context = useContext(AppContext);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
isUserAuthenticated(context.token).then(setIsAuthenticated).catch(setIsAuthenticated);
});
if (isAuthenticated) {
return <Route {...routeProps} />;
} else {
return <Redirect to={{ pathname: "login" }} />;
}
}
const isUserAuthenticated = async (token: any): Promise<boolean> => {
try {
const response = await VerifyAuthentication(token);
console.log("VerifyAuthentication", response);
if (response.status !== 200) {
return false;
}
return true;
} catch (err) {
return false;
}
};
App.tsx
class App extends Component<Props, State> {
renderRouter = () => {
return (
<Router>
<Switch>
<ProtectedRoute exact path="/" component={Dashboard} />
<Route exact path="/login" component={Login} />
</Switch>
</Router>
);
};
render(): ReactElement {
return (
<div className="App">
<AppProvider>
<Theme>
<Sidebar>{this.renderRouter()}</Sidebar>
</Theme>
</AppProvider>
</div>
);
}
}
Upvotes: 0
Views: 105
Reputation: 218877
Presumably this redirects the user to a route which doesn't have this component:
return <Redirect to={{ pathname: "login" }} />;
Which means the component is unmounted, or generally unloaded from active use/memory. And this always happens, because this condition will never be true
:
if (isAuthenticated) {
Because when the component first renders that value is explicitly set to false
:
const [isAuthenticated, setIsAuthenticated] = useState(false);
So basically what's happening is:
It's not entirely clear how this component is intended to fit into your overall structure, but you're going to need to change that structure. Either checking for authentication would need to be synchronous or you'd need to wait for the asynchronous operation to complete before redirecting. An example of the latter could be as simple as:
export function ProtectedRoute({ ...routeProps }: ProtectedRouteProps): ReactElement | null {
const context = useContext(AppContext);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
isUserAuthenticated(context.token)
.then(x => {
setIsAuthenticated(x);
setIsLoading(false);
})
.catch(e => {
setIsAuthenticated(false);
setIsLoading(false);
console.log(e);
});
});
if (isLoading) {
return <div>Loading...</div>;
} else if (isAuthenticated) {
return <Route {...routeProps} />;
} else {
return <Redirect to={{ pathname: "login" }} />;
}
}
In that scenario a separate state value of isLoading
is used to track whether the asynchronous operation is still taking place, so the component "waits" until the data is loaded before deciding to redirect the user or not.
But overall I don't see why the authentication check can't be synchronous. Something higher-level, such as a provider component that wraps the entire application structure within <App/>
, can have this same logic above, essentially performing the async operation and keeping the result in state. Then that state can be provided via useContext
or Redux or even just passing as props to all child components.
You shouldn't need to re-check for authentication over and over in child components. That's an application-level concern.
Upvotes: 2
Reputation: 12787
You can use a variable to check component is mount or unmount when call setIsAuthenticated
useEffect(() => {
let isMouted = true;
isUserAuthenticated(context.token)
.then((val) => isMouted && setIsAuthenticated(val))
.catch(setIsAuthenticated);
return () => {
isMouted = false;
};
});
Upvotes: 0