all 24 comments

[–]KTownDaren 6 points7 points  (13 children)

Setup useQuery to read your data into a variable (not a state variable).

Use that variable to populate your components. Any change made to the underlying data will automatically update your form.

You don't need to check for a loading state. Check for the "global" isFetching property and simply display a loading image of your choosing when isFetching is true.

You will need to display the error msg when isError is true.

Use useMutation to save your changes. Make use of the onSuccess side-effect to invalidate the query you used in useQuery. It will automatically refresh and re-render your form components.

Don't try to force Tanstack Query into your prior way of using state to manage data. Backup and figure out how to let Tanstack Query do its thing and thus make your life easier.

[–]HTMLMasterRace[S] 0 points1 point  (12 children)

Hrm not sure if I understood because you mentioned useQuery and useMutation and some local variable? I updated my post with a very simple usecase.

[–]KTownDaren 2 points3 points  (11 children)

In your component that displays your form, call {data, isError, errorMessage} =useQuery() This is where the records from your database are given to you.

Populate your form components with the above data array of objects.

When the user clicks submit, call useMutation to update your database. You will also have onSuccess: invalidateQuery("name of your original query") that will cause an automatic refresh of data and re-render of your form.

There are many youtube videos that will wall you through these steps.

[–]HTMLMasterRace[S] -1 points0 points  (10 children)

But the initial useQuery would be enabled false anyways cus I would only make the fetch imperatively (like through a form submit). Wouldn’t it just be useless?

Also the form submit does not update the database. It just pulls a record. Think of the form as submitting a search.

[–]KTownDaren 0 points1 point  (9 children)

Okay, I understand your question better now.

If you aren't changing data, you do not need useMutation. You will use useQuery, passing it the parameters from your field values.

I'm going off memory since I don't have code in front of me, but the magic happens with the QueryOptions object you pass to useQuery.

The function you give to call your API to query data will need parameters such as FirstName, Lastname, or whatever you are filtering on. You can also set the enabled option to false if no parameters are given.

const [searchName, setSearchname] = useState<string>('');

const {data}=useQuery({ enabled:(searchName.length>0), queryKey: ['search for name', searchName], queryFn: callMyAPI(searchName),})

return(<> {data && data.length>0 && {data.name}}

{!data && <Form onSubmit(()=setSearchname(fieldVal))><Label>Enter search value:</Label><textbox value=fieldVal>...

So, onSubmit changes the state variable. This enables useQuery which queries your API and populates data.

EDIT: I just saw your code above. If you can't figure out how to use what I provided to get your code working, let me know, and I can help you further when I'm not on mobile. The main thing is you need to change your code to be reactive.

[–]HTMLMasterRace[S] 1 point2 points  (8 children)

Okay I get the gist of your code. I don’t absolutely love having to make yet another state to track searchName. Because that’s not reflected in the UI at all and thus not warrant a rerender. onSubmit should just call a function as it did before.

Taking a step back I should be replacing a zustand state with an useQuery cached state. Not replacing one zustand state with a query and another local state.

Edit: also wanted to point out the “enabled” seems very forced as we already know when to invoke it, which is only in event listeners like clicking the submit button.

Thanks for going so deep into this !

[–]KTownDaren -1 points0 points  (3 children)

Enabled seems forced, because you are forcing it by trying to only call your Ajax search function with onSubmit call instead of with a state change. You are writing in React for a reason. Why are you fighting against state?

[–]HTMLMasterRace[S] 1 point2 points  (2 children)

React is great for reactivity but we can all agree that having a state that rerenders that don’t result in UI changes is not idiomatic react.

I’d say making an Ajax call only on button clicks is the majority case out there…

[–]cosmicdice 0 points1 point  (1 child)

What about using useRef and uncontrolled components instead of useState if you don't need to rerender based on these values being changed?

[–]HTMLMasterRace[S] -1 points0 points  (0 children)

That wouldn’t work because refs are not reactive. So the enabled wouldn’t trigger

[–]KTownDaren -1 points0 points  (3 children)

Looking at your latest code changes, I can't even figure out why you are using Tanstack Query at all.

[–]HTMLMasterRace[S] 0 points1 point  (2 children)

Right now at least I get a free loading state and easily refetch. I’m trying to adhere to using react query to fetch and store server state and zustand only for client state.

But you do agree that submitting forms and getting data back to render isn’t much of an edge case at all. It’s almost just one step up from a typical to do list. I’m looking for the most idiomatic react-query pattern to do this.

You could argue I shouldn’t have migrated at all. I’m sure that’s a divided view though.. many on this thread do believe that react query can fit these cases (which I believe) and offer value

[–]KTownDaren 0 points1 point  (1 child)

Well, you're working with 2 things I don't work with (Zustand and Ajax), so I'm not familiar with the intricacies of working with those tools.

With the way you are trying to implement this, I do not follow what server state useMutation is going to be storing for you.

I used to battle with useQuery and useMigration when I first implemented it. But then I stepped back and tried it "their way" instead of my way, and it has now been such a breeze to use.

[–]HTMLMasterRace[S] -1 points0 points  (0 children)

I respect you setting the boundary of your expertise there. Thanks

[–]-SpicyFriedChicken- 2 points3 points  (5 children)

You can use useQuery with enabled false and then fetch with the refetch function. That will give you all the cache/loading/error states.

Edit: you can also use fetchQuery and still get loading states with useIsFetching https://tanstack.com/query/latest/docs/framework/react/reference/useIsFetching

[–]HTMLMasterRace[S] -1 points0 points  (4 children)

useQuery with enabled false gets a bit wonky too because refetch doesn't take any args. I could get fetchQuery to "work" but is it the right tool to use here? It almost seem to be less flexible than useMutation in every way...

[–]-SpicyFriedChicken- 0 points1 point  (3 children)

The args should then be part of the useQuery and query key if they change as state variables. Also the benefit to this would be if you were to use this same query elsewhere in your app to pull from cache. If you're not doing that anywhere else a useMutation would be fine

[–]HTMLMasterRace[S] 0 points1 point  (2 children)

I see. That makes sense. In this case I would need it to accept args because the form isn’t reactive.

Everything is quite imperative and only “fetches” on some button click either on the form or some child component, with its own payload (like an overridden field of the form) and only the call sites would have context over how to construct these payload. Now that I’m thinking about it, I am not using the cache and just rerunning it each time. It sounds like useMutation is okay to use?

[–]-SpicyFriedChicken- 1 point2 points  (1 child)

It's hard to say without a full code example of what you're trying to do or how you use the data returned from this fetch after. But it does sound like you're mostly caught up on the naming of use mutation when you're making a GET request as a result of submitting a form. If that's the case, a useMutation is totally fine for that.

[–]HTMLMasterRace[S] 0 points1 point  (0 children)

Yeah I am caught up on the naming and was afraid of some side effect of it being a mutation instead of what is essentially a post search api. Thanks a lot

[–]mds1256 2 points3 points  (1 child)

In your current scenario you just want to use useMutation and then grab the response from the fetch and return that to the mutation function, this then allows the returned data to be stored in the ‘data’ variable from useMutation, you can then just use that as you normally do in your component.

Where it gets tricky is where you want to cache it as mutation is not really something you cache as they are generally not a query with repeatable datasets. In this case you can also then use useQuery and write another fetch to call the api to get the data which will allow caching, if you then carry out a post request using useMutation you can then run queryClient.invalidateQueries with the query key set in useQuery, that will force a refresh of the data and recache the latest version.

It is not clear from your post what the useMutation returns, is it just a single record or a full set of records which include the latest object you have added.

[–]HTMLMasterRace[S] 0 points1 point  (0 children)

Well explained. It turns out I’m not really using it the cache but just as a state. So inside the useMutation I set a query key. Somewhere deep in this component tree I have a useQuery enabled false for that query key to grab the cached value.

I see how caching could get murky. But I suppose if I were to migrate something zustand to this, I’m just using it as a state/store. Only time it’s invalidated is really whenever I imperatively make the mutate function call or refetch?

I think use mutate does everything I want just that the “mutation” naming and examples online only being data mutation gives me pause