Should I use setState inside useEffect with React Query (or TRPC)?

Lets say I want to fetch a product and set the result to a zustand store.
//Get zustand store setter
const { setSelectedProduct } = useCustomerTransactionStore();

//Using TRPC
const { isLoading, isError, error, data:productData } = api.product.getById.useQuery({ productId: 5 })
//Get zustand store setter
const { setSelectedProduct } = useCustomerTransactionStore();

//Using TRPC
const { isLoading, isError, error, data:productData } = api.product.getById.useQuery({ productId: 5 })
Since tkdodo mentions we shouldn't be using onSuccess (it will be deprecated fully soon), is the only approach to just add:
useEffect(() => {
setSelectedProduct(productData)
}, [productData])
useEffect(() => {
setSelectedProduct(productData)
}, [productData])
Is there a better way to approach this? TkDodo article: https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose
Breaking React Query's API on purpose
Why good API design matters, even if it means breaking existing APIs in the face of resistance.
29 Replies
Brendonovich
Brendonovich12mo ago
There’s no need to store it in separate state - you’ve already got the value in productData Also do yourself a favour and don’t destructure the result of useQuery/useMjtation
DYELbrah
DYELbrah12mo ago
Thank you @brendonovich , the main reason I store it in a different state is because that state has a ton of actions that can ultimately modify it before we ultimately use a mutation to update a bunch of records on our DB. So in my client, I need to store the data in a particular way that allows me to modify it via different actions in my App, then ultimately use that final modified data to save it. Essentially need to fetch data, transform it and hydrate my Zustand store. The Zustand store contains many actions such as deleting particular pieces of that data.
Brendonovich
Brendonovich12mo ago
Ahh basically for an edit form, that makes sense
DYELbrah
DYELbrah12mo ago
Yeah for sure if I just needed to read the same data I'd just call the same useQuery since the fetched value is stored in cache
Brendonovich
Brendonovich12mo ago
Is the useEffect for when the data loads or for when the selected id changes?
DYELbrah
DYELbrah12mo ago
the useEffect should be for when the data loads. In my example I hard coded a '5' but in my actual app that would be a variable as you mention. So ideally if the id changes, the useQuery fetches the new value and the new value is set via the useEffect
Brendonovich
Brendonovich12mo ago
Hmm, useEffect does work but I think I'd implement it a bit differently, something along the lines of giving the form a key and using Suspense to wait for the data to be loaded Though storing the product in zustand does kind of mess with that Might be worth spreading the product data into a new object when you assign it though, unless you're already doing that in zustand
DYELbrah
DYELbrah12mo ago
Yeah that would sounds good but I'm sure big enough apps need to hydrate some sort of store if they have a lot of logic/modifications going on within the client Yeah in zustand the setter I use only accepts that product and hydrates wherever I need the product to be stored
Brendonovich
Brendonovich12mo ago
I'm curious what sort of modifications you need to do? I'd just hook all of the product data up to React Hook Form and let it take care of everything for me
DYELbrah
DYELbrah12mo ago
I'm using Formik actually
Brendonovich
Brendonovich12mo ago
And I just wouldn't render the form until the product had loaded
DYELbrah
DYELbrah12mo ago
But should be similar Nice, yeah maybe the overall way I'm setting up the data is kind of flawed
Brendonovich
Brendonovich12mo ago
Oh interesting - Formik with an external zustand store?
DYELbrah
DYELbrah12mo ago
I'll give you the actual example: I'm building a customer transaction page for a shop that sells wooden sheets / planks. When I first visit the page, I am fetching the base transaction info (such as customer name and order number....) and I am also fetching all the transaction line items. To easily find which transaction line item I want to edit, in Zustand I setup a Map where the key is the line item number and the value is the all information that makes up that line item. So when I click on the line item, I can quickly load the selected line item information.
Brendonovich
Brendonovich12mo ago
Hmm, I wonder if you'd have a better time using the vanilla trpc client inside your actions instead of using the react query integration Since you're basically trying to synchronise 2 separate stores and the react lifecycle
DYELbrah
DYELbrah12mo ago
yeah basically at any time we have the actual server state, and the state of the form on the client (prior to the user saving).
DYELbrah
DYELbrah12mo ago
Just found this, going to give a read through the comments see if someone has something similar too https://github.com/TanStack/query/discussions/5279
GitHub
RFC: remove callbacks from useQuery · TanStack query · Discussion #...
I wanted to write an RFC, but after I announced it on twitter, I decided to make a blogpost. This should cover most of the questions around that topic: https://tkdodo.eu/blog/breaking-react-querys-...
DYELbrah
DYELbrah12mo ago
@brendonovich do you have any examples of how you're handling fetching data for forms before ultimately updating and saving to the server?
Brendonovich
Brendonovich12mo ago
i've got a whole application for ya haha
Brendonovich
Brendonovich12mo ago
GitHub
spacedrive/interface/app/$libraryId/settings/library/locations/$id....
Spacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust. - spacedrive/interface/app/$libraryId/settings/library/locations/$id.tsx at m...
DYELbrah
DYELbrah12mo ago
Thanks! Taking a look!
DYELbrah
DYELbrah12mo ago
One thing that is concerning about using suspense though is from https://tanstack.com/query/v4/docs/react/guides/suspense says not to use in prod 😦
Suspense | TanStack Query Docs
NOTE: Suspense mode for React Query is experimental, same as Suspense for data fetching itself. These APIs WILL change and should not be used in production unless you lock both your React and React Query versions to patch-level versions that are compatible with each other. React Query can also be used with React's new Suspense for Data Fetching...
Brendonovich
Brendonovich12mo ago
oh well, i like it
DYELbrah
DYELbrah12mo ago
Yeah it's pretty neat
Brendonovich
Brendonovich12mo ago
alternative is to just do the data fetch in Component and conditionally render the form
DYELbrah
DYELbrah12mo ago
Yeah there's a few options, overall for my example, I think I need to rethink how I'm even handling all the data to begin with. Might have to rethink the overall Zustand store
Brendonovich
Brendonovich12mo ago
Yeeeah mixing multiple solutions for storing server state gets dicey
DYELbrah
DYELbrah12mo ago
Ultimately reread the original article and found that we shouldn't really by state syncing using both react query and something like redux (or zustand) so for now this would be the way even though it sucks
export function useTodos(filters) {
const { dispatch } = useDispatch()

const query = useQuery({
queryKey: ['todos', 'list', { filters }],
queryFn: () => fetchTodos(filters),
staleTime: 2 * 60 * 1000,
})

React.useEffect(() => {
if (query.data) {
dispatch(setTodos(query.data))
}
}, [query.data])

return query
}
export function useTodos(filters) {
const { dispatch } = useDispatch()

const query = useQuery({
queryKey: ['todos', 'list', { filters }],
queryFn: () => fetchTodos(filters),
staleTime: 2 * 60 * 1000,
})

React.useEffect(() => {
if (query.data) {
dispatch(setTodos(query.data))
}
}, [query.data])

return query
}
Want results from more Discord servers?
Add your server
More Posts
any typescript genius can help with writing a wrapper function for a SvelteKit loader ?the way that the auth library i'm working with works, is that it recommends reading in the auth fromArchiving on serverless functionsDoes anyone know any zipper/achiver library that works on serverless functions?Admin dashboardHow could I create an admin dashboard where if the user is not authenticated with next-auth it goes Clerk - can’t get types of unsafeMetadata properties on user object?I have some unsafeMetadata stored on the user object for users in my t3-app. When I try to access anhow to get rid of t3 icon in Head component?I mean, it's a good icon, but I'd like to have my own in place. I have <link rel='icon' href='/my-iMutation alters user in database then redirects to homepage. How can i force user session to updateI have a page /form when i press the submit button i activate a mutation that updates a field on useStoring a Triple Nested ObjectSo my prisma schema is as follows: ```prisma model Game { id Int @id @default(autoincrement()) Cold starts on Edge?Hi, I have an API route that I made using Planetscale's database-js library. Here's the code: ``` imYear old IntelliJ bug is actively making my development experience miserableIntelliJ has had some type of CPU bug while trying to parse mildly complex Typescript types for abouHow can I let users download files uploaded to UploadThing?Hey all. Total noob with anything to do with files in the web dev space. I adopted UploadThing in my