Why my custom hook causes infinite data refetching? - reactjs

My component gets the hashed-id from the query string, then calls api with that hash to fetch a post for review.
eslint forces me to add my custom hook to dependency array.
fetchpost();
}, [query]);
But doing this causes an infinite loop. In order to stop it I need to disable this eslint rule, as seen below.
// component file
const history = useHistory();
const dispatch = useDispatch();
const query = useQuery();
const [post, setPost] = useState(null);
const [hash, setHash] = useState(null);
useEffect(() => {
const fetchpost = async () => {
const hash = query.get("hashed_id");
const post = await fetchReviewPost(
`/api/posts/${hash}/review`
);
setHash(hash);
setPost(post);
};
fetchpost();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// utils file
import { useLocation } from "react-router-dom";
export const getCurrentURL = () => {
return document.URL;
};
export const useQuery = () => {
const queryString = useLocation().search;
return new URLSearchParams(queryString);
};
Dan Abramov writes An infinite loop may also happen if you specify a value that always changes in the dependency array.
Is that the case here? Is query reference different on every render? And why eslint wants to put it in a dependency array?
He also says removing a dependency you use (or blindly specifying []) is usually the wrong fix. Which I sort of did by disabling the eslint rule.
Any thoughts?

If you really want to keep sticking to eslint suggestions and using the useQuery hook, here is an alternative way:
// component file
const history = useHistory();
const dispatch = useDispatch();
const q = useQuery();
const [query] = useState(q);
const [post, setPost] = useState(null);
const [hash, setHash] = useState(null);
useEffect(() => {
const fetchpost = async () => {
const hash = query.get("hashed_id");
const post = await fetchReviewPost(
`/api/posts/${hash}/review`
);
setHash(hash);
setPost(post);
};
fetchpost();
}, [query]);
At this point the query value keeps constant across the subsequent function calls.
However, I'd remove the useQuery hook, and place its content straight into the fetchpost function.

Related

How do i fix the react useEffect problem?

Iam a new to react and recently i just have just been practicing react by making a simple shopping cart functionality
this is part of the code i get the error from:
const MainShop = () => {
const [products, setProducts] = useState([]);
const [category, setCategory] = useState('');
const [sort, setSort] = useState('');
const [filteredProducts, setFilteredProducts] = useState([]);
useEffect(() => {
const fetchItems = async () => {
const data = await fetch('https://fakestoreapi.com/products');
const items = await data.json();
console.log(items);
setProducts(items);
setFilteredProducts(products);
};
fetchItems();
}, []);
the error says:
Line 42:6: React Hook useEffect has a missing dependency: 'products'. Either include it or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setFilteredProducts' needs the current value of 'products' react-hooks/exhaustive-deps
how do i solve this problem?
Basically, you should first learn what is useEffect Dependency Array. Also in your question define the states you are using and where are you getting products what is the difference with fetched items. If they are different is it necessary to keep them in the same useEffect hook? Note that you can have several effect hooks in one component with different dependencies array
But as a fast hack, if you want the fetch to be triggered only once when the component mounts, disable the dependency error like below.
useEffect(() => {
const fetchItems = async () => {
const data = await fetch('https://fakestoreapi.com/products');
const items = await data.json();
console.log(items);
setProducts(items);
setFilteredProducts(products);
};
fetchItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
If according to filters the fetch should be triggered again
useEffect(() => {
const fetchItems = async () => {
const data = await fetch(`https://fakestoreapi.com/products?filter=${filters}`);
const items = await data.json();
console.log(items);
setProducts(items);
setFilteredProducts(products);
};
fetchItems();
}, [filters]);

React Hook useEffect has a missing dependency for redux action as parameters

I found many similar questions here about React Hook useEffect has a missing dependency. I have already checked them, but I didn't find solutions as I faced. I want to pass redux thunk function as a parameter to React custom hook.
Below is my code and it is working fine. But, I got dependency missing warning, I don't want to add ignore warning eslint. If I add dispatchAction to dependency array list, it is dispatching again and again because redux thunk asyn function has fulfilled, reject, pending.
Custom Hook
const useFetchData = (dispatchAction, page) => {
const dispatch = useDispatch();
const [loadMoreLoading, setLoadMoreLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState();
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
fetchData();
}, [dispatch, page]);
return [loadMoreLoading, errorMsg]; // it is asking for adding dispatchAction.
My component
const SomeListing = ({userId}) => {
const [page, setPage] = useState(1);
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}),
page,
);
}
So, is there any way to be able to add redux thunk function in react custom hook?
The function fetchPropertyByUserId, when called i.e. fetchPropertyByUserId({userId: userId, page: page}), returns an "actionCreator" function.
Hence, when you call this function at the place of first parameter of your hook useFetchData, it returns a new "actionCreator" function each time (we know that hooks are called at each render):
In SomeListing.jsx:
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}), // <-- Here: it returns a new "actionCreator" function at call (render)
page,
);
And, as soon as you put this function (first parameter of the hook i.e. dispatchAction) as a dependency of useEffect, it should cause an infinite execution of the effect because, now we know, that dispatchAction is getting created (hence, changed) at every render.
In useFetchData.js:
export const useFetchData = (dispatchAction, page) => {
// ...
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true)
const resultAction = await dispatch(dispatchAction)
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message)
}
setLoadMoreLoading(false)
}
fetchData()
}, [dispatch, dispatchAction, page]) // <-- "dispatchAction" added here
// ...
How to fix it?
Pass a memoized actionCreator function:
In SomeListing.jsx:
export const SomeListing = ({ userId }) => {
const [page, setPage] = useState(1)
// Here: "fetchPropertyByUserIdMemo" is memoized now
const fetchPropertyByUserIdMemo = useMemo(
() => fetchPropertyByUserId({ userId: userId, page: page }),
[page, userId]
)
const [loadMoreLoading, errorMsg] = useFetchData(fetchPropertyByUserIdMemo, page)
// ...
}
How about extracting the fetch method from useEffect?:
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
useEffect(() => {
fetchData();
}, [fetchData]);

How can I map a prop array that has been set in the useEffect hook

I am trying to map an array that is in the form of a props from another component but I cannot get the data out. I seem to have done everything rightly but I cant see anything on my screen.
const [products, setProducts] = useState([])
const [featuredProduct, setFeaturedProduct] = useState([]);
useEffect(() => {
return ()=>{
const responseProductData = async()=>{
const result = await axios.get('/products.json')
const data = result.data;
setProducts(data)
setFeaturedProduct(products.filter(product=>product.featured===true))
}
responseProductData()};
}, [products]);
return (
<>
<Header/>
<Featured featured={featuredProduct}/>
<Product/>
</>
)
I am setting featured to featured product for the featured component then in the component itself I am calling props.featured and trying to map it out but brings nothing. In the console, I can see the array but it keeps rerendering. I feel that might be the cause.
Still trying to get used to React.
The issue you are running into is that you have products in the dependency array of the effect. This means that every time the products state changes, the effect is re-run. Which changes products again. So, your component is in a rendering loop.
You can use data directly rather than products in the effect so you can remove the dependency, which will fix the loop.
const [products, setProducts] = useState([])
const [featuredProduct, setFeaturedProduct] = useState([]);
useEffect(() => {
const responseProductData = async()=>{
const result = await axios.get('/products.json')
const data = result.data;
setProducts(data)
setFeaturedProduct(data.filter(product=>product.featured===true))
}
responseProductData()};
}, []);
return (
<>
<Header/>
<Featured featured={featuredProduct}/>
<Product/>
</>
)
Alternatively, you can figure out the featured product from the products with useMemo if you don't need it as a full stateful value.
const [products, setProducts] = useState([])
const featuredProduct = useMemo(()=>products.filter(product=>product.featured===true),[products])
useEffect(() => {
const responseProductData = async()=>{
const result = await axios.get('/products.json')
setProducts(result.data)
}
responseProductData()};
}, []);
You're returning a function from your useEffect which will fire when you unmount the component, so you won't see the data.
import React, { useEffect, useCallback } from 'react';
...
const getProducts = useCallback(async () => {
const result = await axios.get('/products.json')
const data = result.data;
setProducts(data)
setFeaturedProduct(products.filter(product=>product.featured===true));
}, [setProducts, setFeaturedProduct]);
useEffect(() => {
getProducts();
}, [getProducts]);

React infinity loop when making HTTP calls using useEffect

I am trying to make 2 HTTTP calls inside a React component that will then call the setters for 2 properties that are defined using useState. I have followed what I thought was the correct way of doing so in order to prevent inifinite rerendering but this is still happening. Here is my code:
function Dashboard({ history = [] }) {
const [teamInfo, setTeamInfo] = useState(null);
const [survey, setSurvey] = useState(null);
const [open, setOpen] = useState(false);
const user = getUser();
const getSurveyHandler = async () => {
const surveyResponse = await getSurveys('standard');
setSurvey(surveyResponse.data);
};
const getTeamInfoHandler = async () => {
const teamInfoResponse = await getTeamInfo(user.teamId);
setTeamInfo(teamInfoResponse);
};
useEffect(() => {
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, [survey, teamInfo]);
As you can see, I have defined the functions outside of the useEffect and passed in the two state variables into the dependency array that will be checked to prevent infinite rerendering.
Can anyone see why this is still happening?
Thanks
You are setting survey and teamInfo in your functions with a dependency on them in your useEffect.
useEffect runs everytime a dependency changes. You are setting them, causing a rerender. Since they changed, the useEffect runs again, setting them again. The cycle continues.
You need to remove those.
useEffect(() => {
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, []);
The only other thing recommended is to move async functions inside the useEffect unless you need to call them from other places in the component.
useEffect(() => {
const getSurveyHandler = async () => {
const surveyResponse = await getSurveys('standard');
setSurvey(surveyResponse.data);
};
const getTeamInfoHandler = async () => {
const teamInfoResponse = await getTeamInfo(user.teamId);
setTeamInfo(teamInfoResponse);
};
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, []);

Correct dependency array for useEffect with React hooks

I am using Create-React-App and the (excellent) use-http for a custom useFetch hook. The goal is to make several API calls upon login to an account area:
const [user, setUser] = useState(null)
const [profile, setProfile] = useState(null)
const [posts, setPosts] = useState(null)
const request = useFetch('/')
const initializeAccount = async () => {
try {
const user = await request.get('api/user/')
const profile = await request.get('api/profile/')
const posts = await request.get('api/posts/')
if (user) {
setUser(user.data)
}
if (profile) {
setProfile(profile.data)
}
if (posts) {
setPosts(posts.data)
}
} catch (e) {
console.log('could not initialize account')
}
}
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
})
I have tried using [] as the dependency array, but I get a linting error saying to move initializeAccount to the dependency array. If I add it, the function runs endlessly.
What is the correct way to setup the dependency array so that this function is called one time? Also, what would be the correct way to handle abort of each of the API calls in this scenario?
My man, in order to run useEffect once for api calls, you have to do it like this:
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
},[])
Hope it helps.

Resources