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...)
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:
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 be undefined
on the first render cycle. If that's where you mount your useState
, it will not reflect the state. So we make sure that the form component is only rendered after data is ready:
function App() {
const { data, isPending, isError } = useQuery({
queryKey: ["name", nameId],
queryFn: async () => {
const consulta = await getFetch(NAME_URI + "/" + nameID);
return name as NameType | undefined;
},
})
if (isPending) return "loading ..."
if (isError) return "error"
// now we have data
return <FormComponent initialData={data} />
}
function FormComponent({ initialData }) {
const [name, setName] = useState(data.name);
}
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:
It's not a must to initialize useState
with any value. What if we just keep it undefined
, meaning "the user hasn't made any changes yet". If it's undefined, we'll derive it from server state:
function App() {
const { data, isPending, isError } = useQuery({
queryKey: ["name", nameId],
queryFn: async () => {
const consulta = await getFetch(NAME_URI + "/" + nameID);
return name as NameType | undefined;
},
})
// initialize with nothing (undefined)
const [localName, setName] = useState()
// derive real name value
const name = localName ?? data.name
}
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.
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:
useEffect(() => {
if (data.name) {
setName(data.name);
}
}, [data]);
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 to Charlie
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).