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
Brendonovich17mo 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
DYELbrah17mo 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
Brendonovich17mo ago
Ahh basically for an edit form, that makes sense
DYELbrah
DYELbrah17mo 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
Brendonovich17mo ago
Is the useEffect for when the data loads or for when the selected id changes?
DYELbrah
DYELbrah17mo 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
Brendonovich17mo 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
DYELbrah17mo 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
Brendonovich17mo 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
DYELbrah17mo ago
I'm using Formik actually
Brendonovich
Brendonovich17mo ago
And I just wouldn't render the form until the product had loaded
DYELbrah
DYELbrah17mo ago
But should be similar Nice, yeah maybe the overall way I'm setting up the data is kind of flawed
Brendonovich
Brendonovich17mo ago
Oh interesting - Formik with an external zustand store?
DYELbrah
DYELbrah17mo 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
Brendonovich17mo 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
DYELbrah17mo 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
DYELbrah17mo 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
DYELbrah17mo 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
Brendonovich17mo ago
i've got a whole application for ya haha
Brendonovich
Brendonovich17mo 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
DYELbrah17mo ago
Thanks! Taking a look!
DYELbrah
DYELbrah17mo 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
Brendonovich17mo ago
oh well, i like it
DYELbrah
DYELbrah17mo ago
Yeah it's pretty neat
Brendonovich
Brendonovich17mo ago
alternative is to just do the data fetch in Component and conditionally render the form
DYELbrah
DYELbrah17mo 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
Brendonovich17mo ago
Yeeeah mixing multiple solutions for storing server state gets dicey
DYELbrah
DYELbrah17mo 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