I want to build a JSON file based on a bunch of recoil values, so I created a function that fetches all the recoilVaules and returns a JSON out of it, and I call it from a component.
The thing is that it gives me an error for using useRecoilValue outside a component.
besides moving the function inside the component (which will make the component look bad because there are a lot of values) or passing all the recoilValue as parameters to the function (same), what more can I do?
This is the general idea -
const ReactCom = () => {
getJson();
....
}
const getJson = () => {
let jsonFile = useRecoilValue(someAtom);
return jsonFile;
}
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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
This should work
const ReactCom = () => {
const json = useGetJson();
....
}
const useGetJson= () => {
let jsonFile = useRecoilValue(someAtom);
return jsonFile;
}
Related
I'm not able to retrieve a context value from a function not in a Component. I receive following exception:
Uncaught (in promise) 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...
I've declared my context.
export const ErrorContext = createContext({})
export const UseErrorContext = () => useContext(ErrorContext)
Set up a provider within my App.js
<ErrorContext.Provider value={{ errorMessage }}>
</ErrorContext.Provider>
And like to set the value from a function like so. But this results in Exception above. This function is in a seperate file and called from different components.
export const MyFunction = async (id) => {
const { errorMessage } = UseErrorContext();
errorMessage = "SOME ERROR MESSAGE";
}
You won't be able to do it this way:
You can't call a hook outside the render phase in react.
Besides, even if you call it this way, the assignment is local and wouldn't affect the context value.
To achieve your desired behavior, you need:
A state to hold the error message along with a state setter
The context value should change each time the error changes
You should grab the state setter from a component calling your hook
Pass your state setter as a callback to your async function
Something like this:
// context
export let ErrorContext = createContext({})
export let useErrorContext = () => useContext(ErrorContext)
// provider
let [errorMessage, setErrorMessage] = useState();
let value = useMemo(() => ({errorMessage, setErrorMessage}), [errorMessage])
<ErrorContext.Provider value={value}>
{children}
</ErrorContext.Provider>
// component
let {setErrorMessage} = useErrorContext();
export const myFunction = async (id, setErrorMessage) => {
setErrorMessage("SOME ERROR MESSAGE");
}
// later, in an event:
myFunction(id, setErrorMessage);
PS: This solution requires you to do a Provider per error message so you can have multiple, or you should change the abstraction.
Another solution which may seem like a promotion to my own library, but it is not, just to show how easily it solves this:
yarn add -D async-states react-async-states
import {useAsyncState, createSource} from "react-async-states"
// myErrorMessage should be unique, or else the library will warn
let myErrorMessageSource= createSource("myErrorMessage");
// in your component
let {state, setState} = useAsyncState(myErrorMessageSource);
// then use the state.data to display it
// in your async function, and your component would react to it
myErrorMessageSource.setState("SOME ERROR MESSAGE");
This is just a basic use case of the library, please read the docs before opt for it.
as the error says :
Hooks can only be called inside of the body of a function component
what I suggest is to create your custom hook
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
now this is possible
export const useMyFunction = async (id) => {
const { errorMessage } = UseErrorContext();
...
}
make sure to only call other Hooks unconditionally at the top level of your custom Hook.
same when calling your custom hook from another component
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
There is a rule that react hooks should not be used inside conditional statements. However, I found that if I rename the function when importing it, this rule doesn't apply. Using zustand, a third-party library, I avoid this rule as follows, but I haven't found any errors yet. When I create a store function in zustand it seems to be implicitly named useStore, but is it ok to rename it to something like this to avoid the rules of react hooks? Here is some code examples.
// zustandModule.js
import create from 'zustand'
const useStore = create(set => ({
bears: 0,
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
}));
// App.jsx
import { useStore } from '../zustandModule';
export default function App({ someBoolean }) {
const count = someBoolean ? useStore((state) => (state.bears)) : 1;
// ^
// Error: React Hook "useStore" is called conditionally. React Hooks must be called in the exact same order in every component render.
return <div>count: {count}</div>
}
No error
// App.jsx
import { useStore as getStore } from '../zustandModule';
export default function App({ someBoolean }) {
const count = someBoolean ? getStore((state) => (state.bears)) : 1;
return <div>count: {count}</div>
}
FWIW, I have a project where I am using Zustand, and I have three stores to store three different types of data. (Note:) I typically end up calling them like the following in my components, for example:
const themeClasses = themeStore((state) => state.themeClasses);
This doesn't raise any errors, and I think it's probably fine.
I am not saying this is a best practice, though. In fact there's a good chance its awful practice, but this is the first time I've been working on a project of this complexity in React and I didn't think I could wrap my head around Redux in time to stay on budget. However, I do keep to the rules of hooks: I only call them inside components, I don't call them conditionally, and I call them from the top. You definitely should not be using a hook rename to get around rules, you'll just end up with code that produces unexpected effects that will take longer to debug.
I have created one functional component that name is TopLocation() and i am calling this function to another component const handleChange = e => {
TopLocation(e.target.value)
}
function TopLocation (cityid=null) {
const[location,setLocation]=useState();
let city={city_id:(cityid==''?localStorage.getItem("location"):cityid)};
useEffect(()=> {
const fetchData= async()=>{
const result = await Locationapi(
city
);
console.log('result',result);
if(result.status==200){
setLocation(result.data);
}
}
fetchData();
},[]);
it's showing error.
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
You might have more than one copy of React in the same app
Don’t call Hooks inside loops, conditions, or nested functions
but you can call react hooks inside of your own custom hook
read more about hooks over here
https://reactjs.org/docs/hooks-intro.html
I am using many useCallbacks in useEffects in the same format syntax and feel like shortening them. So, I map them in this hook. effects is an array of useCallback functions.
import React from 'react';
const useEffects = (effects: Function[]) =>
effects.map((effect) =>
React.useEffect(() => {
effect();
}, [effect])
);
export default useEffects;
Hooks can't be defined inside an array.
I wrote an article recently about the rendering order of hooks. https://windmaomao.medium.com/understanding-hooks-part-4-hook-c7a8c7185f4e
It really goes down to below snippet how Hooks is defined.
function hook(Hook, ...args) {
let id = notify()
let hook = hooks.get(id)
if(!hook) {
hook = new Hook(id, element, ...args)
hooks.set(id, hook)
}
return hook.update(...args)
}
When a hook is registered, it requires an unique id, as in line notify(). Which is just a plain i++ placed inside the Component where the hook is written inside.
So if you have a fixed physical location of the hook, you have a fixed id. Otherwise the id can be random, and since the render Component function is called every render cycle, a random id is not going to find the right hook in the next render cycle.
That is why if can not be written before the hook statement either. Just try
const Component = () => {
const b = true
if (!b) return null
const [a] = useState(1)
}
You should get similar error.