How to use useCallback inside custom hook - reactjs

got little issue here. I'm exporting a custom hook, which uses other hooks, but I get "Unhandled Rejection (Error): Invalid hook call..." Error. Looks like I'm not doing something wrong... I'm using hook inside hook, so why is this error being thrown?
const customHook = async () => {
const { values } = useValues()
const testCallback = useCallback(async () => {
const value = values
return value
}, [value])
return testCallback
}
export default customHook

I think the issue can be resolved by just changing the case of your component's name, try renaming it to "CustomHook". There's a rule with react hooks that you can not call use hook inside a function or component whose name is in Camel Case.

Related

React how to set context from a function. Invalid hook call

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

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

Recoil useRecoilValue outside component

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

How to use react-query useQuery inside onSubmit event?

im noob using react query and react with typesript, i don't know how to solve this:
React Hook "useQuery" is called in function "onSubmit" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter.
export const LoginForm = () => {
const { handleSubmit, control } = useForm<IFormInput>({defaultValues: defaultValues, resolver: yupResolver(schema)});
const onSubmit = ({email, password}: IFormInput) => {
const {data, isLoading, error} = useQuery('loginUser', () => startLogin({email, password}));
console.log(data);
console.log(error);
};
...
...
...
}
export const startLogin = ({email, password}: IFormInput) => (
axios.post(loginEndpoint, {email, password}).then(res => res.data)
);
I’d like to add that for logging in a user, you probably don’t want a query but a mutation. Logging someone in is likely not a GET request and it has side effects (it makes the user be logged in), so useMutation is for that. You can define useMutation on the top of your functional component (in accordance with the rules of hooks) and invoke the returned mutate function in the callback:
export const LoginForm = () => {
const { mutate, isLoading } = useMutation(variables => startLogin(variables))
const onSubmit = ({email, password}: IFormInput) => {
mutate({ email, password })
};
First of all, the problem is just an eslint react plugin (rules-of-hooks) complaining about your use of a hooks.
You can tell it to shutup by adding the following comment above the line where the error is happening:
// eslint-disable-line react-hooks/rules-of-hooks
However...
this is only going to prevent you from understanding the correct way of using hooks in react. Before you proceed with the above, this is the correct way to handle your use case.
First move the hook outside of the function, and instead of useQuery, use useMutation:
export const LoginForm = () => {
const { handleSubmit, control } = useForm<IFormInput>({defaultValues: defaultValues, resolver: yupResolver(schema)});
const { data, error, mutate: loginUser } = useMutation(startLogin);
const onSubmit = ({email, password}: IFormInput) => {
loginUser({email, password});
console.log(data);
console.log(error);
};
}
Now that that's done, as I mentioned before, reason you got the complaint from eslint is because the rules-of-hooks plugin has plain no nonsense rule that assumes any function starting with use* must be a react hook. It also assumes that such a function is only used inside a react component hence the part of the message that says:
React component names must start with an uppercase letter.
The reason this rule exists is explained here. Essentially it comes down to the following:
rules-of-hooks assumes that any custom react hook will make use of one of the existing react hooks (useEffect, useState, useMemo, etc...), and since these hooks affect the state of the component, it is assumed that your custom hook does soo to (even though you may not have written the useQuery hook).
React functional components rely on order of hooks to maintain state. If that order is broken at any point during the render of the component, the component is unable to maintain its internal state.
Therefore, specify all potential mutations at the top of your components.
Your onSubmit() doesn't look like being executed inside a React Component, and you also need to rename your component to a PascalCase.
In most cases, rename your component to PascalCase will fix the issue.

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.

Resources