T
TanStack•4y ago
like-gold

Best patterns for initializing form data from a query

The pattern I've typically used is some container component is the launching point for a form. It has a list of data and the user clicks on one to edit it, which navigates to a form (modal or another page, whatever). The way I manage this logic is usually something like this
const query = useItemsQuery();
const [selectedItem, setSelectedItem] = useState<Item | null>(null);

function handlePickItemToEdit(item) {
setSelectedItem(item);
}

function handleSubmit() {
itemMutation.mutate(selectedItem, {
onSuccess() {
setSelecteditem(null);
}
});
}

if (selectedItem) {
return <ItemForm item={selectedItem} onSubmit={handleSubmit} />
}
// else display the list of items to pick one to edit
const query = useItemsQuery();
const [selectedItem, setSelectedItem] = useState<Item | null>(null);

function handlePickItemToEdit(item) {
setSelectedItem(item);
}

function handleSubmit() {
itemMutation.mutate(selectedItem, {
onSuccess() {
setSelecteditem(null);
}
});
}

if (selectedItem) {
return <ItemForm item={selectedItem} onSubmit={handleSubmit} />
}
// else display the list of items to pick one to edit
The thing is, this assumes the list will always load first, but what if the user navigates directly to the edit page via hard page refresh? What's a good way to initialize the form?
const {id} = useParams();
const serverItem = useItemQuery(id).data;
const [item, setItem] = useState<Item | null>(null);

useEffect(() => {
setItem(prev => prev ? prev : serverItem);
}, [serverItem]);
// ^ I hate doing stuff like this, seems super jank
const {id} = useParams();
const serverItem = useItemQuery(id).data;
const [item, setItem] = useState<Item | null>(null);

useEffect(() => {
setItem(prev => prev ? prev : serverItem);
}, [serverItem]);
// ^ I hate doing stuff like this, seems super jank
Is there a better pattern to try instead? I hope this makes sense.
3 Replies
optimistic-gold
optimistic-gold•4y ago
I think I'm covering this here: https://tkdodo.eu/blog/react-query-and-forms
React Query and Forms
Forms tend to blur the line between server and client state, so let's see how that plays together with React Query.
like-gold
like-goldOP•4y ago
All my forms are controlled and we unfortunately aren't using anything like react-hook-form, and our forms are quite large so I don't want to do value ?? query.data?.value for every field. I suppose I could try the infinite stale time thing, but then I have to make extra query hooks for everything I want to use in a form. Our forms autosave after a bit of inactivity, so they are constantly updating the query cache for the query used by the form, so the stale time thing wouldn't work now that I think about it. The forms are also really complicated and dynamic. Maybe my question is more about forms in general and less about RQ. I'll have to think about it. 🤔
optimistic-gold
optimistic-gold•4y ago
I would split this into two components then?
const {id} = useParams();
const serverItem = useItemQuery(id).data;
const [item, setItem] = useState<Item | null>(null);

useEffect(() => {
setItem(prev => prev ? prev : serverItem);
}, [serverItem]);
// ^ I hate doing stuff like this, seems super jank
const {id} = useParams();
const serverItem = useItemQuery(id).data;
const [item, setItem] = useState<Item | null>(null);

useEffect(() => {
setItem(prev => prev ? prev : serverItem);
}, [serverItem]);
// ^ I hate doing stuff like this, seems super jank
like:
const {id} = useParams();
const serverItem = useItemQuery(id).data;

// handle loading / error etc

<ItemForm initialItem={serverItem.data} />
const {id} = useParams();
const serverItem = useItemQuery(id).data;

// handle loading / error etc

<ItemForm initialItem={serverItem.data} />
ItemForm can then have the useState that initializes with initialItem. if you need the ItemForm to update it's state with server data if a server refresh comes in, I would consider the key prop: https://tkdodo.eu/blog/putting-props-to-use-state#3-fully-uncontrolled-with-a-key
Putting props to useState
Part deux of the useState pitfalls series is here, showing patterns to help solve the common use-case of initializing state with a prop.

Did you find this page helpful?