So I am following Brad Traversy's React course, but it's a little old and I am using hooks and modern React for my own project, but I have a problem. When I enter the Detail page for a user, it calls a function to get the information of that user from an api. However, it turns out I am calling this function hundreds of times, even with useEffect. I will share my code, but I am not sure what I am doing wrong! If anyone can advise me on why this is re-rendering thousands of times, it would definitely be helpful. Thank you!
This is the function that is being called by Detail (setLoading is a useState function, as well as setSingleUser.
const getUser = username => {
setLoading(true);
axios
.get(
`https://api.github.com/users/${username}?client_id=
${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(res => {
axios
.get(
`https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&
client_id=${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(repores =>
setSingleUser({ ...res.data, repos: repores.data }),
);
});
setLoading(false);
};
This is where I call it in Detail
const Detail = ({ getUser, user, loading }) => {
const { username } = useParams();
useEffect(() => {
getUser(username);
}, [username, getUser]);
I have also tried useEffect with an empty dependency array and no dependency array, but it does not like any of these options.
As per your question, you seem to be defining getUser directly within the Parent component and passing it to child component.
Since you update state within the getUser function, the parent component will re-render and a new reference of getUser will be created. Now that you are passing getUser as a dependency to useEffect the useEffect runs again as getUser function has changed.
To solve this, you must memoize the getUser function in parent and you can do that using the useCallback hook
const getUser = useCallback(username => {
setLoading(true);
axios
.get(
`https://api.github.com/users/${username}?client_id=
${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(res => {
axios
.get(
`https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&
client_id=${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(repores =>
setSingleUser({ ...res.data, repos: repores.data }),
);
});
setLoading(false);
}, []);
Once you do that your code should work fine
Just include the username in the dependency array. Remove the function getUser since when the username is changed, the return is executed, again when getUser is called, username changes and again return is executed.
Try using this code:
useEffect(() => {
getUser(username);
}, [username]);
This is happening because of the second parameter to useEffect [username, getUser]. The array of dependencies for which the useEffect must be called is being called within your useEffect directly, which then triggers the useEffect again. So, The getUser is called again. Which then calls useEffect again. And therefore its stuck in a loop. You are pretty much calling a function (getUser) again inside of a function (useEffect) that you want to call each time (getUser) is called.
You need to add an if condition in the useEffect which makes sure the getUser() function gets called only when username is false or null or undefined, or you can also change the array that you are passing to the useEffect. You can even take advantage of the setSingleUser() that you have in the .then(). And can add the values of those in the if check or else in the array.
Related
On a page load I have two useEffects. Both are executing at load, where the first one can possibly set a state, that should trigger the second useEffect one further time. But actually it won't.
Actually it should trigger, as it executes in two cases: When i change the order of these useEffects (could be a solution, but why???), or when i comment out the void getOrPostOnWishlist();, thus when removing the async call from the useEffect. But why is that a problem here?
Here some example code snippet with some comments:
...
const setItemIdToBeHandled = (itemId: number | undefined) =>
setState((prevState) => ({...prevState, itemIdToBeHandled: itemId}));
...
// async, called on second useEffect
const getOrPostOnWishlist = async () => {
if (state.itemIdToBeHandled) {
// if there is an item to be handled, retrieve new wishlist with added item
await addItemToNewWishlist(state.itemIdToBeHandled);
} else if (!state.wishlist) {
// if no wishlist is locally present, check if wishlist exists on api
await checkForPresentWishlist();
}
};
// possibly setting state
React.useEffect(() => {
const urlItemId = UrlSearchParamsHelper.wishlistItemId;
if (urlItemId) {
console.log("found item id in url:", urlItemId);
setItemIdToBeHandled(urlItemId);
}
}, []);
// on state change, but also on load
React.useEffect(() => {
console.log("condition:", state.itemIdToBeHandled); // sticks on 'undefined'
void getOrPostOnWishlist(); // when commented out, above console will show 'undefined', and then an itemId (considering the first useEffect sets the state);
}, [state.itemIdToBeHandled]);
This led to the following output:
But when just commenting out the async call in the second useEffect, this led to:
Googled around, and also tried useCallback, but that didn't work. Doesn't seem to be the issue here, since it's somewhat not about the content of the called function, but about the very fact, that the calling useEffect is not even executed.
It feels like even without await inside the useEffect, a useEffect is still blocked, when it has executed an async function.
Or am i missing something? If some more details are needed, let me know
Here is a useEffect hook that I used in reactjs:
Problem: The useEffect calls fetchAllCategories() endlessly. Infact, over 1000 requests until I terminate. I only want it to run on page mount and when I click on the button to change catName state. What could I be doing wrong?
const [catName, setCategories] = useState([]);
const categoryRef = useRef();
useEffect(()=>{
const fetchAllCategories = async () =>{
try{
const res = await axios.get(`${BASE_URL}/category`)
return setCategories(res.data);
}catch(err){
}
}
fetchAllCategories()
}, [catName])
//create new category
const createNewCategory = async ()=>{
const categoryName = {
catName: categoryRef.current.value
}
try{
const response = await axiosPrivate.post(`${BASE_URL}/category`, categoryName, { withCredentials: true,
headers:{authorization: `Bearer ${auth}`}
})
return setCategories([response.data])
}catch(err){
}
}
The button that triggers changes in catName
<button onClick={ createNewCategory} className='button-general'>Create</button>
You trigger the effect to be run on change of catNames, and then change catNames from the effect itself. This results in the endless self-triggering of the effect.
One solution could be to make your effect depend on nothing:
useEffect(() => {
....
}, []);
Thank you all for pointing out the issue to me. Since I now know the issue, I have been able to fix it. All I did was create another state and made the useEffect to depend on it. Then, whenever the button is clicked, I change the state to the opposite of the initial state. This works and doesnt cause endless calls.
If you absolutely need catName as a dependency, this can work for you:
useEffect(() => {
const fetchAllCategories = async () => {
try {
const res = await axios.get(`${BASE_URL}/category`);
return setCategories(res.data);
} catch (err) {}
};
fetchAllCategories();
}, [JSON.stringify(catName)]);
This is happening because you update the catName state inside the useEffect using setCategories(res.data) this keeps triggering your useEffect thus an infinite loop
You programmed it to run endlessly.
REASON
See, this React hook useEffect has two parameters:
Effect, the callback function which is called whenever the component renders.
List of Dependencies on which the useEffect hook depends and re-renders if any such dependency is updated.
Now, in your case, you've passed catName as a dependency, whose state is updated when the button is clicked. This causes useEffect to call the callback function which calls fetchAllCategories() which also update catName. And this causes an endless loop.
SOLUTION
Just remove the dependency from useEffect as:
useEffect(() {
...
}, []);
Now, updating catName won't cause the effect (The Callback function) to be called endlessly.
I have a functional component in React:
export default function (id) {
const [isReady] = useConfig(); //Custom hook that while make sures everything is ok
useEffect( () => {
if(isReady)
renderBackend(id);
});
async function renderBackend(id) {
const resp = await getBackendData(id);
...
...
}
}
Now, I am passing some props to the Functional Component like this:
export default function (id, props) {
const [isReady] = useConfig(); //Custom hook that while make sures everything is ok
useEffect( () => {
if(isReady)
renderBackend(id);
});
async function renderBackend(id) {
const resp = await getBackendData(id, props); // Passing props to backend
...
...
}
}
Here the props are dynamic based on user input and changes time on time. But my code here is only rendering for the first prop, not on subsequent props. I want to call the backend every time props get updated or being passed. I think we might use useEffect for this, but not totally sure. And I cannot replicate this is codeSandbox as the real code is very complex and have trimmed down to mere basics.
Change
useEffect( () => {
if(isReady)
renderBackend(id);
});
to
useEffect( () => {
if(isReady)
renderBackend(id);
}, [id]);
so useEffect function runs every time id changes
As you do not put useEffect dependency, it will be executed for every re-render (state changed, ...)
So to execute the code within the useEffect every props change, put it to the useEffect dependencies list.
useEffect( () => {
if(isReady)
renderBackend(id);
}, [id, props]);
best practice is destructure your props and put only the affected value in the dependencies.
When using React.useEffect() hook consider that you have to pass the dependencies to gain all you need from a useEffect. The code snippet is going to help you to understand this better.
useEffect(() => {
console.log('something happened here');
}, [one, two, three]);
every time that one of the items passed to [one, two, three] you can see something happened here in your browser developer tools console.
I also should mention that it is not good idea at all to pass complex nested objects or arrays as a dependency to the hook mentioned.
I am trying to set the state of a variable "workspace", but when I console log the data I get an infinite loop. I am calling the axios "get" function inside of useEffect(), and console logging outside of this loop, so I don't know what is triggering all the re-renders. I have not found an answer to my specific problem in this question. Here's my code:
function WorkspaceDynamic({ match }) {
const [proposals, setProposals] = useState([{}]);
useEffect(() => {
getItems();
});
const getItems = async () => {
const proposalsList = await axios.get(
"http://localhost:5000/api/proposals"
);
setProposals(proposalsList.data);
};
const [workspace, setWorkspace] = useState({});
function findWorkspace() {
proposals.map((workspace) => {
if (workspace._id === match.params.id) {
setWorkspace(workspace);
}
});
}
Does anyone see what might be causing the re-render? Thanks!
The effect hook runs every render cycle, and one without a dependency array will execute its callback every render cycle. If the effect callback updates state, i.e. proposals, then another render cycle is enqueued, thus creating render looping.
If you want to only run effect once when the component mounts then use an empty dependency array.
useEffect(() => {
getItems();
}, []);
If you want it to only run at certain time, like if the match param updates, then include a dependency in the array.
useEffect(() => {
getItems();
}, [match]);
Your use of useEffect is not correct. If you do not include a dependency array, it gets called every time the component renders. As a result your useEffect is called which causes setProposals then it again causes useEffect to run and so on
try this
useEffect(() => {
getItems();
} , []); // an empty array means it will be called once only
I think it's the following: useEffect should have a second param [] to make sure it's executed only once. that is:
useEffect(() => {
getItems();
}, []);
otherwise setProposal will modify the state which will trigger a re-render, which will call useEffect, which will make the async call, which will setProposal, ...
I have a problem when using the useEffect hook, it is generating an infinite loop.
I have a list that is loaded as soon as the page is assembled and should also be updated when a new record is found in "developers" state.
See the code:
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = async () => {
await api.get('/developers').then(response => {
setDevelopers(response.data);
});
};
// This way, the loop does not happen
useEffect(() => {
getDevelopers();
}, []);
// This way, infinte loop
useEffect(() => {
getDevelopers();
}, [developers]);
console.log(developers)
If I remove the developer dependency on the second parameter of useEffect, the loop does not happen, however, the list is not updated when a new record is found. If I insert "developers" in the second parameter of useEffect, the list is updated automatically, however, it goes into an infinite loop.
What am I doing wrong?
complete code (with component): https://gist.github.com/fredarend/c571d2b2fd88c734997a757bac6ab766
Print:
The dependencies for useEffect use reference equality, not deep equality. (If you need deep equality comparison for some reason, take a look at use-deep-compare-effect.)
The API call always returns a new array object, so its reference/identity is not the same as it was earlier, triggering useEffect to fire the effect again, etc.
Given that nothing else ever calls setDevelopers, i.e. there's no way for developers to change unless it was from the API call triggered by the effect, there's really no actual need to have developers as a dependency to useEffect; you can just have an empty array as deps: useEffect(() => ..., []). The effect will only be called exactly once.
EDIT: Following the comment clarification,
I register a developer in the form on the left [...] I would like the list to be updated as soon as a new dev is registered.
This is one way to do things:
The idea here is that developers is only ever automatically loaded on component mount. When the user adds a new developer via the AddDeveloperForm, we opportunistically update the local developers state while we're posting the new developer to the backend. Whether or not posting fails, we reload the list from the backend to ensure we have the freshest real state.
const DevList: React.FC = () => {
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = useCallback(async () => {
await api.get("/developers").then((response) => {
setDevelopers(response.data);
});
}, [setDevelopers]);
useEffect(() => {
getDevelopers();
}, [getDevelopers]);
const onAddDeveloper = useCallback(
async (newDeveloper) => {
const newDevelopers = developers.concat([newDeveloper]);
setDevelopers(newDevelopers);
try {
await postNewDeveloperToAPI(newDeveloper); // TODO: Implement me
} catch (e) {
alert("Oops, failed posting developer information...");
}
getDevelopers();
},
[developers],
);
return (
<>
<AddDeveloperForm onAddDeveloper={onAddDeveloper} />
<DeveloperList developers={developers} />
</>
);
};
The problem is that your getDevelopers function, calls your setDevelopers function, which updates your developers variable. When your developers variable is updated, it triggers the useEffect function
useEffect(() => {
getDevelopers();
}, [developers]);
because developers is one of the dependencies passed to it and the process starts over.
Every time a variable within the array, which is passed as the second argument to useEffect, gets updated, the useEffect function gets triggered
Use an empty array [] in the second parameter of the useEffect.
This causes the code inside to run only on mount of the parent component.
useEffect(() => {
getDevelopers();
}, []);