I have a useEffect function in the Layout component which fetch my userData and store it in redux-store but it is triggered with every route changes.
I want the useFffect function to run the first time and don't trigger again when I change my routes. This is not right with sending more useless requests to the server. How can I do that?
This is my Layout component:
import { useEffect, useState } from "react";
import Navbar from "../navbar/navbar";
import Footer from "../footer/footer";
import Head from "next/head";
import { useCookies } from "react-cookie";
import { useDispatch, useSelector } from "react-redux";
import axios from "axios";
import { getUserData } from "../../redux/actions/Auth";
const Layout = ({ children }) => {
const [cookie, setCookie] = useCookies(["token"]);
const dispatch = useDispatch();
useEffect(() => { // this useEffect triggers every time i route !!
const { token } = cookie;
if (token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
dispatch({ type: "IS_LOGGED_IN" });
dispatch(getUserData(token));
}
}, []);
return (
<div className="layout" dir="rtl">
<Head>
<meta name="description" content="" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="true"
/>
<link
href="https://fonts.googleapis.com/css2?family=Noto+Kufi+Arabic:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
type="text/css"
charset="UTF-8"
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css"
/>
<link
rel="stylesheet"
type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css"
/>
</Head>
<Navbar />
{children}
<Footer />
</div>
);
};
export default Layout;
You are using the useEffect hook which triggered on component did mount, also will trigger with every route change that's the default and correct behavior of useEffect hook since you use it on Layout component and it will re-render with every route changes.
There are some solutions to handle these cases like implementing getUserData(token) on a parent component of Layer component which does not get re-render on every route change.
Another solution is to use another state variable to hold the API call status. you have an IS_LOGGED_IN action which may change your reducer like this:
case IS_LOGGED_IN:
return {
...state,
isUserLoggedIn: true
}
So, you need to get and check this sample property on useEffect before dispatching the getUserData:
const isLoggedIn = useSelector(state => state.user.isUserLoggedIn) // get isUserLoggedIn from the state
useEffect(() => {
if (!isLoggedIn) { // -----> getUserData if user is not logged in
const {token} = cookie;
if (token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
dispatch({ type: "IS_LOGGED_IN" });
dispatch(getUserData(token));
}
}
}, [isLoggedIn]);
once you use the Layout component, the isLoggedIn value is false (since the user is not logged in) so the dispatch(getUserData(token)) method will call and get the data. after that, the isLoggedIn will change to true so the conditional statement in the useEffect check it and doesn't let to dispatching getUserData again.
Note: You may need another action with the proper procedure on reducer to toggle between the user is logged in or logged out on the application.