export default function MyQuestions() {
const router = useRouter();
const [auth, setAuth] = useState(false);
const checkAuth = async () => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) return setAuth(true);
return;
};
checkAuth();
This is a part of a React component where I execute the checkAuth function. I thought it should execute only once but that is not the case. It is executed 4 times and if I remove the returns it is executed even more than 10 times and I don't understand why. In js a function that reaches the end should stop automatically.
Why does this happen?
In this code router is of Next.js
What are the conditions under which the check should be re-run? This is what useEffect is intended for. useEffect accepts a function to run the desired effect, and a list of dependencies to specify when an effect should be run -
import { useRouter } from ...
import { useEffect, useState } from "react"
function MyQuestions() {
const router = useRouter()
const [auth, setAuth] = useState(false)
useEffect(async () => {
const loggedInUsername = await getUsername()
if (router.query.username === loggedInUsername)
setAuth(true)
}, [getUsername, router.query.username, setAuth])
return <>...</>
}
Any free variable inside the effect must be listed as a dependency of the effect. There's one issue however. setAuth will be a new function each time MyQuestions is rendered. To ensure setAuth will be the same for each render, we can use useCallback -
import { useRouter } from ...
import { useEffect, useCallback, useState } from "react"
function MyQuestions() {
const router = useRouter()
const [auth, setAuth] = useState(false)
const authenticate =
useCallback(_ => setAuth(true), [])
useEffect(async () => {
const loggedInUsername = await getUsername()
if (router.query.username === loggedInUsername)
authenticate()
}, [getUsername, router.query.username, authenticate])
return <>...</>
}
Now the effect will only re-run when getUsername, router.query.username or authenticate changes. Considering getUsername and authenticate are functions and should not change, we can expect that the effect will only re-run when router.query.username changes.
I haven't used nextjs but i suppose it happens because it is executed on every render of the router component.
If you want to use it once, just call it in a use effect when the component mounts.
useEffect(() => {
checkAuth();
}, []) // This will run once, when the component mounts
There is no need to return the setState call:
const checkAuth = async () => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) setAuth(true);
};
Also because you are calling the checkAuth() function right after you call it. Setting state in React causes a re-render. So the reason it is executed 4 to 10 times is because your MyQuestion component re-renders when you setState(), and when it rerenders, is hits the checkAuth() function again and the cycle repeats.
As mentioned put the functionality in a useEffect() with an empty dependency array:
useEffect(() => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) return setAuth(true);
}, [])
Related
I've created a custom hook which I want to use to fetch data with. Now I've come a long way with some help from several blog articles, but there's just one thing I want to improve on. I have a custom hook which fetches data using a useEffect hook. This way the data is fetched upon render, and when for example query params change. Now the useEffect has a caveat. When I include a dependency array with anything in it, it's all fine, but I get a warning that the hook is dependent on a value. I don't like warnings so I add the value to the dependency array, but for some reason then it just keeps rerendering. Below is my useApi hook:
import { useState, useEffect } from "react";
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import api from "./config/api-config";
const useApi = (axiosParams: AxiosRequestConfig) => {
const [response, setResponse] = useState<AxiosResponse>();
const [error, setError] = useState<AxiosError>();
const [loading, setLoading] = useState(axiosParams.method === "GET");
const fetchData = async () => {
try {
const result = await api.request(axiosParams);
setResponse(result);
} catch (err: any) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
console.log("Render useApi");
axiosParams.method === "GET" && fetchData();
}, [axiosParams.method, fetchData]);
return { response, error, loading, fetchData };
};
export default useApi;
And this is where I'm using it:
import { FC, useState } from "react";
import { Wrapper } from "./home.style";
import { HomeProps } from "./home.types";
import useApi from "../../../api/useApi";
const Home: FC<HomeProps> = () => {
const [query, setQuery] = useState<String>();
const { response, loading, error, fetchData } = useApi({
method: "GET",
url: "/books/v1/volumes",
params: {
q: "",
},
});
return <Wrapper></Wrapper>;
};
export default Home;
I've tried using a callback hook for the fetchData function, but then the issue with the dependency array moves from the useEffect to the useCallback. Does anyone know how I should handle this?
To answer as an answer:
Infinite reexecution of useApi hook is happening due to the object passed as a parameter is recreated on each component rerendering.
const { ... } = useApi({
method: "GET",
url: "/books/v1/volumes",
params: {
q: "",
},
});
It will work fine in case a parameter is a plain string or a number. But in case of normal object or array, for example, you need to preserve a reference to them. You can either move this config out of functional component scope, i.e. just place it above it (if it is not meant to be modified), either preserve it with useMemo or useState hook.
// const useApiConfig = useMemo<AxiosRequestConfig>(() => {
const useApiConfig = useMemo(() => {
return {
method: "GET",
url: "/books/v1/volumes",
params: { q: "" }
};
}, [])
/* ... */
const { response, loading, error, fetchData } = useApi(useApiConfig);
Additionaly, due to fetchData is in depsArray of useEffect hook - it is important to wrap fetchData into useCallback due to without it fetchData will be recreated on each rerender and useEffect will be triggered. And due to reexport of the fetchData from the hook - component that is using it will also have an issues with rerender.
Usually (when reexport is not needed) method like fetchData is just placed inside of the useEffech hook itself (as a const function).
const useApi = (axiosParams: AxiosRequestConfig) => {
const [response, setResponse] = useState<AxiosResponse>();
const [error, setError] = useState<AxiosError>();
const [loading, setLoading] = useState(axiosParams.method === "GET");
const fetchData = useCallback(async () => {
try {
const result = await api.request(axiosParams);
setResponse(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [axiosParams]);
useEffect(() => {
console.log("Render useApi");
axiosParams.method === "GET" && fetchData();
}, [axiosParams.method, fetchData]);
return { response, error, loading, fetchData };
};
I'm trying to fetch an api on a custom reactjs hook using Axios. I keep getting twice the response as undefined and after that twice as a successful fetch with the data. The undefined breaks my app.
Btw I'm fetching from the randomuser api.
import axios from "axios";
import { useState, useEffect } from "react"
export const useFetch = (url) => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState('')
const getData = () => {
setLoading(true)
try {
axios.get(url)
.then(response => setData(response.data));
setLoading(false)
} catch (error) {
setError(error)
}
};
useEffect(() => {
getData()
}, [url])
return {loading, data, error}
}
Trying to use it here and map over it
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useFetch } from '../custom_hooks/useFetch';
const PersonDetails = () => {
const { loading, data , error } = useFetch('https://randomuser.me/api?results=20');
const { results } = data;
const { id } = useParams();
const [person, setPerson] = useState({})
useEffect(() => {
const newPerson = results?.find(person => person.login.uuid === parseInt(id))
setPerson(newPerson)
console.log(newPerson)
}, [])
return (
<div>
{person.name.first}
</div>
)
}
export default PersonDetails
This is the thing I actually Im trying to do, but now because it is undefined, I get that cannot read properties of undefined...
When the effect runs you:
setLoading(true)
Send the Ajax request
setLoading(false)
Later, then the Ajax response arrives you:
setData(response.data)
Since you depend on loading to determine if data is set or not, it breaks.
There are two things you could do:
Move setLoading(false) inside the then callback so it doesn't get set until after you have setData(response.data)
Get rid of loading entirely and base your logic off data being undefined or having a different value.
you should define the getData function inside the useeffect or pass it in dependency array and wrap the function by usecallback to avoid unnecessary rerenders.
you should use abortcontroller in case of cancelations and to have cleanup function in useeffect. (in this case it's better to define getdata body in useeffect)
useEffect(() => {
const controller = new AbortController();
const getData = async () => {
setLoading(true)
try {
await axios.get(url, {signal: controller.signal})
.then(response => setData(response.data));
} catch (error) {
setError(error)
}
}
getData()
return()=>controller.abort()
},[url]}
you can read more about fetching data with hooks in following url and where to setloading and other needed states.
https://www.robinwieruch.de/react-hooks-fetch-data/
Just in case, this solution helped me : https://github.com/axios/axios/issues/2825#issuecomment-883635938
"The problem in my case was caused by React development server.
The strict mode in react caused the issue!
I had to remove the strict mode
This solved the problem of sending double requests!
The strict mode checks are only run in development mode.
Doc: https://reactjs.org/docs/strict-mode.html
"
I have a peculiar problem when using the useEffect-hook in React Native. I have a functional component, which has one useEffect-hook that fetches data for pinpoints and another that then rearranges the pinpoints (filteredPinpoints) into a useable format. filteredPinpoints is updated three times, but the first two times, the object is empty.
Now the weird behaviour: if I comment out dispatch(organiseRoutes(...)) in the second useEffect, this useEffect is called three times, but if I want to execute the dispatch function, the useEffect is only called twice. Since I return early if filteredPinpoints is empty, the code never reaches the dispatch.
EDIT: Also, when I implement dispatch(organiseRoutes(...)), the app freezes, only showing the (spinning) ActivityIndicator, but leaving me unable to navigate to the previous screen again.
What do I have to change, so that the useEffect is run every single time filteredPinpoints is updated?
import { View, ActivityIndicator } from 'react-native';
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getRouteData, organiseRoutes } from '../utils';
export default function RoutePreviewScreen() {
const dispatch = useDispatch();
const [loadingData, setLoadingData] = useState(true);
const currentRouteID = useSelector(state => state.currentRouteID);
const filteredPinpoints = useSelector(state =>
// Uses ObjectFilter from https://stackoverflow.com/questions/5072136/javascript-filter-for-objects/37616104
ObjectFilter(state.allPinpoints, pinpoint => pinpoint.Route_ID == state.currentRouteID)
);
const dispatch = useDispatch();
// This updates state.allPinpoints.
useEffect(() => {
(async function myFirstAsyncFunction() {
await dispatch(getRouteData(currentRouteID));
})();
}, [currentRouteID]);
useEffect(() => {
if (Object.keys(filteredPinpoints).length === 0) {
return
}
console.log("Could EXECUTE now!!")
// If the following line is commented out, the useEffect executes a third time.
// However, only in the third run, filteredPinpoints is not a empty object.
// If it is not commented out, it simply refuses to execute a third time.
dispatch(organiseRoutes(filteredPinpoints));
setLoadingData(false)
}, [filteredPinpoints]);
if (loadingData) { return (<View><ActivityIndicator/></View>)}
return(<ComponentUsingOrganisedRoutes/>)
Looks like your filteredPinpoints is an object. useEffect does not do a deep equality check on object, for that you'll have to deal with it differently.
You can use a dependency like: [JSON.stringify(filteredPinpoints)], which would do a better check and won't be as slow as a deep equality check.
ref: https://twitter.com/dan_abramov/status/1104414272753487872
Okay, fixed it. Pretty stupid mistake. Basically, organiseRoutes() affects filteredPinpoints, so useEffect is caught in an infinite loop. I guess in these scenarios, console.log() is not called because it's being blocked.
I introduced a new state-hook organisedData aside to loadingData, to give the second useEffect another value to check for before it executes the dispatch. Once organisedData is true, it's going to stop executing and thereby stop triggering itself now.
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { getRouteData, organiseRoutes } from '../utils';
export default function RoutePreviewScreen() {
const dispatch = useDispatch();
const [loadingData, setLoadingData] = useState(true);
const currentRouteID = useSelector(state => state.currentRouteID);
const filteredPinpoints = useSelector(state =>
ObjectFilter(state.allPinpoints, pinpoint => pinpoint.Route_ID == state.currentRouteID)
);
// New:
const [organisedData, setOrganisedData] = useState(false);
useEffect(() => {
(async function myFirstAsyncFunction() {
// New:
setLoadingData(true)
await dispatch(getRouteData(currentRouteID));
// New:
setOrganisedData(false)
})();
}, [currentRouteID]);
useEffect(() => {
// New:
if (!loadingData || organisedData) {
return
}
if (Object.keys(filteredPinpoints).length === 0) {
return
}
// New:
setOrganisedData(true)
dispatch(organiseRoutes(filteredPinpoints));
// New:
setLoadingData(false)
// Adjusted:
}, [organisedData, loadingData, filteredPinpoints]);
if (loadingData) { return (<View><ActivityIndicator/></View>)}
return(<ComponentUsingOrganisedRoutes/>)
Why the fetchData function is defined inside the useEffect and not outside ?
Link:
https://github.com/zeit/next.js/blob/canary/examples/with-graphql-faunadb/lib/useFetch.js
import { useState, useEffect } from 'react'
export default function useFetch(url, options) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options)
const json = await res.json()
setData(json)
} catch (error) {
setError(error)
}
}
fetchData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url])
return { data, error }
}
I would have done that:
import { useState, useEffect } from 'react'
export default function useFetch(url, options) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
// Defined outside of useEffect
// `u` instead of `url` for not overlapping
// with the one passed in useFetch()
const fetchData = async (u) => {
try {
const res = await fetch(u, options)
const json = await res.json()
setData(json)
} catch (error)
setError(error)
}
}
useEffect(() => {
// Using url as an argument
fetchData(url)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url])
return { data, error }
}
It seems easier to read and better organized. I'm thinking it's maybe an anti-pattern or something else ?
I typically define the functions inside the useEffect, there are several reasons for that
By definining the function outside of the use effect, you either need to disable exhaustive-deps and risk accidentally having a stale function or you need to useCallback to make the function not-update every render
If the function is only used in the useEffect, you don't need to recreate the function on every render as that's just wasted cycles
It's easier to work with cleanup on the asynchronous functions by defining it within useEffect as you can define variables that are able to be modified within the effect.
On that last one, for instance, you can do some actions to prevent state being called when the effect cleans up.
You could also use AbortController with fetch to cancel the fetch.
import { useState, useEffect } from 'react'
export default function useFetch(url, options) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
let isUnmounted = false;
const fetchData = async () => {
try {
const res = await fetch(url, options)
const json = await res.json()
if(!isUnmounted) setData(json)
} catch (error) {
if(!isUnmounted) setError(error)
}
}
fetchData()
return ()=>{isUnmounted = true;}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url])
return { data, error }
}
Technically fetchData should be a dependency for useEffect according to React Hooks rules. But if you add it, it will give you an error saying it will cause useEffect to run on every re-render as the function is recreated IF this hook is defined inside a component.
But since it is defined outside a component, my understanding is that the function will not be recreated. Then just add the fetchData as a dependency.
If this useEffect was used inside a component you could either just pass the function inside the useEffect or add the dependency and cover your fetchData with a useCallback.
I am having this problem to build my app. Anyone knows what is wrong?
React Hook useEffect has a missing dependency: 'conectar'. Either include it or remove the dependency array react-hooks/exhaustive-deps
const GraficoEquivalenteNovo = props => {
const [equivalenteNovos, setEquivalenteNovos] = useState([]);
const [data, setData] = useState([]);
async function conectar() {
const resposta = await ConexaoGraficoEquivalenteNovo(props);
setEquivalenteNovos(resposta[0]);
setData(resposta[1]);
}
useEffect(() => {
conectar();
}, [props]);
return (....)
};
Your hook depends on the function connectar which is declared outside the hook, but is internal to the render process. It is re-manufactured on every render. Therefore, React sees it as a volatile dependency. You could have the function outside your component but since the function itself uses state hooks and depends on props, move it into the effect hook.
useEffect(() => {
async function conectar() {
const resposta = await ConexaoGraficoEquivalenteNovo(props);
setEquivalenteNovos(resposta[0]);
setData(resposta[1]);
}
conectar();
}, [props]);