React Hook "useEffect" cannot be called inside a callback error occurs - reactjs

The error below sometimes occurs I don't know why. Every code seems to work but only an error occurs.
React Hook "useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
import { useEffect, useState } from 'react'
import { useAuthContext } from 'src/contexts/Auth'
import { brandRepository } from 'src/repositories/BrandRepository'
export default function TestBrand() {
const [brands, setBrands] = useState<any[]>()
const { currentUser } = useAuthContext()
useEffect(() => {
if(!currentUser) return
useEffect(() => {
brandRepository.list(currentUser.id).then(async (docs: any[]) => {
if(!docs) return
await setBrands(docs)
})
}, [])
}, [currentUser])
if(!currentUser) {
return <div>Loading...</div>
}
return (
<div>test</div>
)
}

You cant call useEffect or other hooks inside a function or CB you can only call hooks inside Function component
https://reactjs.org/docs/hooks-rules.html
Blockquote
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls

You cannot use useEffect inside another useEffect. Try using both the hooks separately as it forbids the rules of hooks. Better don't use the second useEffect as I do not see any specific use of it. Refer this https://reactjs.org/docs/hooks-rules.html

It seems that in useEffect you are making a network request for one time if the currentUser exists. In this case you don't need 2 useEffect. if can do it like this
useEffect(() => {
if(!currentUser) return
brandRepository.list(currentUser.id).then(async (docs: any[]) => {
if(!docs) return
await setBrands(docs)
})
}, [currentUser])

Related

getting error when I run useQuery inside the useEffect hook in react-query

When I run the below code I am getting: Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
useEffect(() => {
if (user.email) {
const { data } = GetUserInfo(user.email);
}
}, [user.email]);
What I am trying to do is until I get the user email I will not run the getUserInfo query.
As mentioned in the other answer, you can only call hooks at the top level in the body of a functional component/custom hook.
I think you can achieve what you want using the enabled option in useQuery to enable/disable it based on a condition.
Docs example:
function Todos() {
const [filter, setFilter] = React.useState('')
const { data } = useQuery(
['todos', filter],
() => fetchTodos(filter),
{
// ⬇️ disabled as long as the filter is empty
enabled: !!filter
}
)
...
}
Reference:
https://tanstack.com/query/v4/docs/guides/disabling-queries
You can't call a hook inside another hook.
useEffect(() => {
if (user.email) {
const { data } = GetUserInfo(user.email);
}
}, [user.email]);
You have to call the hook outside useEffect hook
There are three common reasons you might be seeing it
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
In your case you are breaking the rule of hooks that Call Hooks from React function components. GetUserInfo is also hook you are calling in a hook you need to call it outside of useEffect()
for reference documentation

Should localstorage be set from within react callbacks?

In a (typescript) react app, I have some hooks for reading and writing to local storage, like this:
import { useEffect, useState } from "react";
...
export const useLocalStorage = (key, defaultValue) => {
const [value, setValue] = useState(() => {
return getStorageValue(key, defaultValue);
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
Elsewhere in the app, I have a UX element that needs to store some data in local storage, as part of an onClick() callback:
myValue, setMyValue = useLocalStorage("MY_KEY", 0);
...
onClick() => {
setMyValue("some data");
}
However, this means calling useEffect() from within a callback, which violates the hook rules.
Is it conventional here to just call localstorage.setItem() directly from withing the callback, or is there a more idiomatic way to refactor this code?
I think you're a little confused about what a hook is. Consider this snipet.
function Button() {
const [wasClicked, setWasClicked] = useState(false);
function handleClick() {
setWasClicked(true) // completely legal.
// this is not "calling a hook"
const [clickedTime, setClickedTime] = useState(Date.now()) // illegal
// this is "calling a hook"
}
return <button disabled={wasClicked} onclick={handleClick}>click!</button>
}
Calling a hook like useState(false) is as you say not permitted within callbacks. This is because the order in which hooks are called is actually super important to React. So, you can't conditionally call hooks, and you cant call them from a callback, you have to call them at the top level of your component.
That being said, setWasClicked is not a hook, it's just a regular function that happens to be returned from a hook. You can call this function from anywhere, because as stated it is not a hook.
In your case, useLocalStorage is a hook, you have to follow the rules of hooks. However, it returns setValue which is not a hook, just a regular function returned by the useState call. That triggers the useEffect callback to run, but it doesn't re-run useEffect. useEffect was called only when you called useLocalStorage.
TLDR:
To answer your question, I would put local storage stuff in a hook. You do want to use the useEffect hook because you don't want to access localStorage on every render, only when dependencies change.

Is it possible to use a custom hook inside useEffect in React?

I have a very basic custom hook that takes in a path an returns a document from firebase
import React, { useState, useEffect, useContext } from 'react';
import { FirebaseContext } from '../sharedComponents/Firebase';
function useGetDocument(path) {
const firebase = useContext(FirebaseContext)
const [document, setDocument] = useState(null)
useEffect(() => {
const getDocument = async () => {
let snapshot = await firebase.db.doc(path).get()
let document = snapshot.data()
document.id = snapshot.id
setDocument(document)
}
getDocument()
}, []);
return document
}
export default useGetDocument
Then I use useEffect as a componentDidMount/constructor to update the state
useEffect(() => {
const init = async () => {
let docSnapshot = await useGetDocument("products/" + products[selectedProduct].id + "labels/list")
if(docSnapshot) {
let tempArray = []
for (const [key, value] of Object.entries(docSnapshot.list)) {
tempArray.push({id: key, color: value.color, description: value.description})
}
setLabels(tempArray)
} else {
setLabels([])
}
await props.finishLoading()
await setLoading(false)
}
init()
}, [])
However, I get an Invariant Violation from "throwInvalidHookError" which means that I am breaking the rules of hooks, so my question is whether you can't use custom hooks inside useEffect, or if I am doing something else wrong.
As far as I know, the hooks in a component should always be in the same order. And since the useEffect happens sometimes and not every render that does break the rules of hooks. It looks to me like your useGetDocument has no real need.
I propose the following solution:
Keep your useGetDocument the same.
Change your component to have a useEffect that has the document as a dependency.
Your component could look like the following:
const Component = (props) => {
// Your document will either be null (according to your custom hook) or the document once it has fetched the data.
const document = useGetDocument("products/" + products[selectedProduct].id + "labels/list");
useEffect(() => {
if (document && document !== null) {
// Do your initialization things now that you have the document.
}
}, [ document ]);
return (...)
}
You can't use a hook inside another hook because it breaks the rule Call Hooks from React function components and the function you pass to useEffect is a regular javascript function.
What you can do is call a hook inside another custom hook.
What you need to do is call useGetDocument inside the component and pass the result in the useEffect dependency array.
let docSnapshot = await useGetDocument("products/" + products[selectedProduct].id + "labels/list")
useEffect(() => { ... }, [docSnapshot])
This way, when docSnapshot changes, your useEffect is called.
Of course you can call hooks in other hooks.
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
But...
You are not using a hook inside another hook.
You realise that you what you pass to useEffect is a callback, hence you are using your custom hook inside the body of the callback and not the hook (useEffect).
If you happen to use ESLint and the react-hooks plugin, it'll warn you:
ESLint: React Hook "useAttachDocumentToProspectMutation" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.(react-hooks/rules-of-hooks)
That being said, you don't need a useEffect at all. And useGetDocument doesn't return a promise but a document.
When calling your hook
const document = useGetDocument("products/" + products[selectedProduct].id + "labels/list");
It will return undefined the first time around, then the document for the subsequent renders as per #ApplePearPerson's answer.

Using hooks in nested functions

I'm trying to rewrite a React class component into a functional hooks-based component, but i cannot figure out how to do it. The component logic and JSX looks something like this:
export class LeftPanel extends React.Component<ILeftPanelProps, ILeftPanelState> {
const [menuItemsFullList, setMenuItemsFullList] = useState([{links: []}] as any[]);
useEffect(() => {
const { links } = props;
setMenuItemsFullList(links);
}, props.links);
....
return (<>
<SearchBox
onChange={_onSearch}
onClear={_onClearSearchBox}
/>
<NavList
listEntries={[menuItems]}
/>
</>)
Where the function i'm currently rewriting is onClearSearchBox:
private _onClearSearchBox() {
this.setState({ menuItems: { ...this.state.menuItemsFullList } });
}
I tried naively rewriting it using hooks which turned the setState into this:
function onClearSearchBox() {
useEffect(() => setMenuItems(menuItemsFullList));
}
This does not work and i do not know how to restructure the code, as i cannot call hooks inside a non-React component function. Moving it into the React component function as an inner function does not work either.
The error message i'm getting is:
Uncaught Invariant Violation: Invalid hook call. Hooks can only be
called inside of the body of a function component...
I believe my mindset is still stuck to the class-based structure, as i cannot figure out how i would go about and refactoring the LeftPanel. How should i go about refactoring _onClearSearchBox to make it work with hooks?
useEffect is the wrong hook for this, from the docs:
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
In your example, you need control over when to want to call the code e.g. on a button click. I'd say useCallback would be the most appropriate hook here:
const onClearSearchbox = useCallback(() => {
setMenuItemsFullList(props.items);
}, [props.items]);
...
<SearchBox onClear={onClearSearchBox} ... />

Why does React Hooks Axios API call return twice?

Why does React Hooks Axios API Call return twice.. even with a check if it's loaded?
I am used to this.setState but trying to understand the reason behind this why it's showing up twice inside my console log.
My code:
import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
const [users, setUsers] = useState({ results: [] });
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
await axios
.get("https://jsonplaceholder.typicode.com/users/")
.then(result => setUsers(result));
setIsLoading(false);
};
fetchData();
}, []);
console.log(
users.status === 200 && users.data.map(name => console.log(name))
);
return <h2>App</h2>;
};
export default App;
For the fire twice, perhaps is one time from componentDidMount, and the other one from componentDidUpdate
https://reactjs.org/docs/hooks-effect.html
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
You did not check 'isLoading' before you fire the API call
// Only load when is not loading
if (!isLoading) {
fetchData();
}
This is actually normal behavior. This is just how functional components work with useEffect(). In a class component the "this" keyword is mutated when the state changes. In functional components with hooks the function is called again, and each function has its own state. Since you updated state twice the function was called twice.
You can read these 2 very in dept articles by one of the creators of react hooks for more details.
https://overreacted.io/a-complete-guide-to-useeffect/
https://overreacted.io/how-are-function-components-different-from-classes/
Even I had a similar issue. in my case useEffect() was called twice because the parent component was re-rendered because of a data change.

Resources