React Hook useEffect has a missing dependency - javascript

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]);

Related

Custom API data fetching hook keeps rerendering

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 };
};

React Hook useEffect has a missing dependency: 'id'. Either include it or remove the dependency array

sorry for the trivial question (maybe) but I've been hitting my head for hours.
where is the mistake?
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { SingleCoin } from '../config/api';
const {id} = useParams();
const [coins, setCoins] = useState();
const {currency, symbol} = CryptoState();
useEffect(()=>{
const fetchCoins = async () => {
const {data} = await axios.get(SingleCoin(id));
setCoins(data);
};
fetchCoins();
}, []);
React Hook useEffect has a missing dependency: 'id'. Either include it or remove the dependency array
Any variable that you use in the useEffect function needs to be in the dependency array, so that it is can be monitored and the useEffect will only be run when that variable changes.
useEffect(()=>{
const fetchCoins = async () => {
const {data} = await axios.get(SingleCoin(id));
setCoins(data);
};
fetchCoins();
}, [id]);
Add id to the array that is the second parameter of useEffect to clear this error.
Reference
useEffect and some other hooks need a dependency array provided. It's the last argument passed as an array. The dependencies tell the hooks which variables or elements to observe for changes. If a dependency changes, the hook should also expect a new behavior and will therefor update.
To fix your issue, you need to provide the id in your dependency array as the warning states like so:
React Hook useEffect has a missing dependency: 'id'
useEffect(()=>{
const fetchCoins = async () => {
const {data} = await axios.get(SingleCoin(id));
setCoins(data);
};
fetchCoins();
}, [id]);

Multiple execution of a function

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);
}, [])

useState | useEffect without setting new state casues additional rerender

I couldn't find any information why this render is called twice ?
const Test: React.FC = () => {
const [myState, setMyState] = useState();
console.log("RENDER TEST");
return <div>test</div>;
};
When I remove
const [myState, setMyState] = useState();
then the component is rendered only once.
The same happens with useEffect:
const Test: React.FC = () => {
useEffect(() => {
console.log("Component mounted");
}, []);
console.log("RENDER TEST");
return <div>test</div>;
};
Without useEffect the render is called only once.
It is happening due to React's StrictMode in order to detect any problems and to want you about them. It runs only in development and not in production. In your application in index.js you will find that your App component is wrapped with React.StrictMode component. You can read more about it https://reactjs.org/docs/strict-mode.html

Define a function inside useEffect or outside?

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.

Categories

Resources