I want to put the "data" from the useQuery response as the initial value of my component.
const {data, isLoading } = useQuery({
queryKey: ["name", nameId],
queryFn: async () => {
const consulta = await getFetch(NAME_URI + "/" + nameID);
return name as NameType | undefined;
},
})
const [name, setName] = useState(data.name)
return
<div>
{ !isLoading ? <p>{JSON.stringify(data.name)}</p> : <p>Is loading...</p>
<p>{name}</p>
</div>
In the moment the component mounts it shows:
"Is loading…"
""
After a while:
"Rob"
""
As the "View" is dynamic the result is right… the "name" state at begin is "" because the query on react query is not "immediate". I try putting a setName(data.name) inside a useEffect that runs only when "isLoading" changes, and it works… but I would think there is a better and more clean way to do it?
CONTEXT EDITED:
Indeed I need to edit/change the "name" retrieved by the query… That’s why I am lookin forward to:
get the name from API (done) > store the data fetched on a state variable > edit that data on the state variable > save the data edited (using mutateAsync function…)
2
Answers
No, if you have a need to edit a local copy then that’s pretty much how you’d handle it within the same component; use a
useEffect
hook to "synchronize" a React state to an external source.An alternative would be to lift the query up higher in the ReactTree and conditionally render the child component with the fetched
name
as a prop.Parent:
Child:
Just be aware that saving passed/fetched props/data like this is generally considered a React anti-pattern. If you have no need to edit/change the local
name
state then the solution is to just reference the returned querydata
directly and forego theuseState
–useEffect
coupling.I have a full blogpost covering this topic: https://tkdodo.eu/blog/react-query-and-forms
In summary, there is two things you can do:
1. Decouple the data fetching component from the form component
The issue is that the
useState
initializer will only run on the first render. Since query data is asynchronous and takes some time to arrive, it will beundefined
on the first render cycle. If that’s where you mount youruseState
, it will not reflect the state. So we make sure that the form component is only rendered after data is ready:That can work, but it means splitting up your component and background refetches will not be reflected. If your cache already has stale data when
App
mounts, you’ll get that data in your state, and new data that comes in later will also not be reflected. So there’s option 2:2. Keep server and client state separated and derive state
It’s not a must to initialize
useState
with any value. What if we just keep itundefined
, meaning "the user hasn’t made any changes yet". If it’s undefined, we’ll derive it from server state:This way,
name
will be whatever the user has given as input, but if they haven’t done anything yet, the server state will be taken as fallback.Why the
useEffect
"solution" is badReact Query is a data synchronization tool, which means it will try to keep what you see on your screen up-to-date with what the source of truth (= the server) holds. It does so sometimes aggressively, with background refetches, e.g. on window focus.
An effect that looks like this:
will always run when data changes, which means it might remove data the user has already changed without saving. On larger forms, this can be troublesome. For example:
data
comes in as{ name: 'Alice' }
, the effect runs and puts that into state.Bob
, but doesn’t save it yet.name
in the database was changed toCharlie
by someone else.Bob
) and overwrite them.This obviously might not be a big issue in small forms, or when data can only be changed by one person, or when background refetches are turned off. But in larger forms, this can be critical to get right.
So the effect adds nothing of value here, it’s just unnecessarily complicated code that will also re-render your component a second time unnecessarily (also won’t matter much).