How to use react-query useQuery inside onSubmit event? - reactjs

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.

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

Is it okay to change the name of the custom React Hook in 3rd party library?

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.

Redux toolkit: Exporting a custom hook from slice file to access actions instead of exporting all actions and then calling it again with dispatch?

Inside a slice file we export all the the actions from that slice. For example:
export const {signoutUser, updateProfile, authenticateUser, clearUserState} = sliceName.actions;
And then we import useDispatch and particular actions from the slice or action file based on your folder structure. For example
import {clearUserState} from './slice';
import { useDispatch } from 'react-redux';
export const Component () {
const dispatch = useDispatch(clearUserState());
//rest component body
}
Now instead I am exporting a custom hook from the slice file like mentioned below:
export const useUserDispatch = () => {
const dispatch = useDispatch();
const userDispatch = {
signoutUser: (data) => dispatch(signoutUser(data)),
updateProfile: (data) => dispatch(updateProfile(data)),
authenticateUser: (data) => dispatch(authenticateUser(data)),
clearUserState: () => dispatch(clearUserState())
};
return {userDispatch}
};
And then i can just import that hook and use like
const {userDispatch}=useUserDispatch();
//inside component
<button onClick={userDispatch.clearUserState()}>Dispatch Button</button>
I just wanted to know if it's something that's not recommended in terms of redux way of writing code or am I doing anything wrong, it works perfectly fine though.
There is nothing wrong with your code. and the question can not be answered to pros and cons based on my experience, redux and all other open-source packages consider base common cases which people are using in the everyday app. There might be some suggestions for improvement but not best-case explanations for every app. you can just consider following and decide yourself
You can not use them as you mentioned useUserDispatch().clearUserState()
e.g.
<button onCleack={useDispatcher().clearUserState}>clear</button>
Hooks can be called conditionally so calling them as this level in the UI part might be conditionally canceled which is a really common case
every time this hook is called a hole new object is created for dispatchers
Also, useDispatch doesn't receive any argument and returns the nearest redux provider store.dispatch. see source code
Note: Redux suggests having one state for all of your apps and doesn't wrap part of your code with multiple providers.
Remember if you need this one of dispatcher (e.g. updateProfile) from some other part of the code, you may need to use this hook which is a waste of resources, or use it directly which shows a is a little bit of uncertainty and be not consistent from the other version (just a little is not a big case).
There are other options to handle these cases.
Remember what redux suggests for one provider, if you accept that you can also write your own dispatcher.
const storeDispatch = store.dispatch
// from slice file export dispatcher instead of action creator
const updateProfile = sliceName.actions.updateProfile;
export const updateProfileDispatcher = (data) => storeDispatch(updateProfile(data))
This can not only be used in your component but also can be used outside of react component inside the logic
Note: using a dispatcher outside the react is not the standard or recommended pattern and you might not want to use it.
You can also pass dispatch as the argument, something like thunk dispatcher
const updateProfile = dispatch => (data) => dispatch(updateProfile(data))
// or alongside of payload data
const updateProfile = (dispatch, data) => dispatch(updateProfile(data))
// also can set the default value of dispatch to store.dis
const updateProfile = (data, dispatch=storeDispatch) => dispatch(updateProfile(data))
// component
const dispatch = useDispatch()
<ProfileComponent onUpdate={updateProfile(dispatch)} />

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.

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