Comments (15)
Huzzah! V1 of React Query is going to nail this pattern. Trust me, you'll love it. Thanks for all of your input everyone!
from query.
This is an interesting situation. My first thought is that what you're referring to in some way is what concurrent mode and suspense do for us. You would essentially be able to hit "Next Page", see a loading indicator in the button, and still see the old page until everything is ready. But I agree, not everyone can move to suspense. That is where I believe your solution from #78 (comment) seems to be perfectly viable, but only assuming you are handling error states properly. Maybe something like this:
function useQuery(
queryKey,
queryFn,
{ initialData, useLastResultAsInitialData, onSuccess, ...queryConfig } = {}
) {
const initialDataRef = React.useRef(initialData)
return useQuery(queryKey, queryFn, {
...queryConfig,
initialData: useLastResultAsInitialData
? initialDataRef.current
: initialData,
onSuccess: useLastResultAsInitialData
? data => {
initialDataRef.current = query.data
if (onSuccess) {
return onSuccess(data)
}
}
: onSuccess,
})
}
function App() {
const [page, setPage] = React.useState(0)
const { refetch } = usePageQuery(['todos'], () => getTodos({ page }), {
useLastResultAsInitialData: true,
})
React.useEffect(() => {
refetch()
}, [refetch])
}
Either way, I see this as more of an issue with non suspenseful react than React Query at this point, mostly because there isn't a one-size-fits-all solution here. Let me know what you think of the above solution and how much convenience/pain something like that would give you and we can discuss more.
from query.
@tannerlinsley I am checking what happens if first page is loaded, but next page couldn't be fetched,
In this case data from previous page are returned, even though there's an error.
And it's actually not related to pagination at all - same happens using pure useQuery
.
I'm wondering if data
should be set to null
, when query failed to fetch?
from query.
Update: I've came up with much better solution:
function usePageQuery(queryKey, queryFn, { initialData, ...queryConfig } = {}) {
const initialDataRef = React.useRef(initialData);
const query = useQuery(queryKey, queryFn, {
...queryConfig,
initialData: initialDataRef.current,
});
initialDataRef.current = query.data;
return query;
}
Basically, I'm using results of previous page as initial data of next page.
This solves all issues listed above, and API of usePageQuery
is identical to useQuery
.
I'm thinking about how to integrate that into React Query and following options come to my mind:
- Add an option to
useQuery
, which will do the sameusePageQuery
does, but within same query key string. - Add
usePageQuery
hook toreact-query
package. Although the name should be more generic (something likeusePrefilledQuery
?) - Document it, add example project and keep it as userland solution
@tannerlinsley what do you think?
from query.
after changing the page - a new query is created, so data is null. This leads to blinking, since data from previous page aren't available yet, so UX experience is poor in this case
I have a similar use case where I'm displaying a list of paginated data (and not using the paginated: true
option in useQuery
). So when switching between pages I just display a loading indicator
const MyComp = () => {
const [page, setPage] = useState(0)
const {data, error} = useQuery(['key', {page}], ({page}) => ...)
if (error) {
return <NotFound />
} else if (!data) {
return <Loading />
} else {
return ... // Handle rendering your data
}
}
from query.
Otherwise I'd suggest using the paginated: true
option and then splitting the returned pages into whatever size you want on the client side
from query.
Hey @JaceHensley
Thanks for your comment!
Otherwise I'd suggest using the paginated: true option and then splitting the returned pages into whatever size you want on the client side
Indeed, I've considered paginated
option for this use case, but it seems like overkill to me.
Especially, when user returns to paginated list and all pages are fetching again.
Assuming, that user returns to non-first page, fetching all previous pages doesn't make a lot of sense, since user sees only one page at a time.
from query.
Yeah that's true, that's kinda why I do it the way in my first comment
from query.
@JaceHensley that's also what I did at first, but as I mentioned above there are 2 issues with that approach:
- "jumpy" UI (event when showing loading indicator)
- incorrect fallback to
initialData
when using SSR
from query.
Oh so it seems like the data
fallsback to config.initialData
in between other queries, I didn't realize that at first. That I'm not sure sure what's expected to happen but could you only set config.initialData
when page === 0
?
import React from "react";
import fetch from "../lib/fetch";
import { useQuery } from "react-query";
function Component({ initialData }) {
const [page, setPage] = React.useState(1);
const { data } = useQuery(
["data", { page }],
async () => await fetch(`/api/data?page=${page}`),
{ initialData: page === 1 ? initialData : undefined }
);
// ...
}
Component.getInitialProps = async () => {
const data = await fetch('/api/data?page=1');
return { initialData: data };
};
For the jumpy UI I'm not really sure but it seems like something that should be handled by the app leveraging the isLoading
/error
values given from useQuery
since there will always be some sort of lag when making a request to the server. Like in my example above, in the actual app I have the loading indicator wrapped in a div that sets the height to be about what I expect the actual content to take up, this helps reduce the jumpiness for me.
Also that usePageQuery
hook looks like it's just storing a previous value of data
so something like this might be better.
const MyComp = ({initialData}) => {
const { data } = useQuery(
["data", { page }],
async () => await fetch(`/api/data?page=${page}`),
{initialData}
);
const prevDataRef = usePrevious(data)
const dataToUse = data || prevDataRef.current
// Use `dataToUse` when mapping/displaying data
}
But I don't know if I'd necessarily use that pattern cause what if one request where page === 2 fails? Now you pagination UI says it's on page 2 but data from page 1 is being displayed. To handle that you could handle when error
is not null and display a message to the user. But now what if a request takes a long time? The pagination UI says it's on page 2 but data is again being displayed for page 1, and the user doesn't know that a request has been fired off or maybe they think the app is being unresponsive or sluggish.
These are just my opinions on the matter and by no means an authoritative opinion :)
from query.
@tannerlinsley this is a good idea and seems to work. My concern is what happens if the next query that is going to be performed is not of the same kind (not the same queryKey)?
The initialData for this new page query are going to be the same as the previous one. Right?
Maybe something like this would catch this case as well
not tested!
function usePageQuery(
queryKey,
queryFn,
{ initialData, useLastDataOfQuery, onSuccess, ...queryConfig } = {}
) {
const initialDataRef = React.useRef(initialData)
const initialLastDataOfQuery = React.useRef(useLastDataOfQuery)
const useLastResultAsInitialData = initialLastDataOfQuery.current === useLastDataOfQuery
return useQuery(queryKey, queryFn, {
...queryConfig,
initialData: useLastResultAsInitialData
? initialDataRef.current
: initialData,
onSuccess: useLastResultAsInitialData
? data => {
initialDataRef.current = data
if (onSuccess) {
return onSuccess(data)
}
}
: onSuccess,
})
}
function App() {
const [page, setPage] = React.useState(0)
const { refetch } = usePageQuery(['todos'], () => getTodos({ page }), {
useLastDataOfQuery: "todos",
})
React.useEffect(() => {
refetch()
}, [refetch])
}
from query.
@tannerlinsley Thanks for looking into this!
What do you think about incorporating this hook into React Query?
I'm trying to prevent developers wasting time on solving this issue, which might be pretty common.
EDIT: Meanwhile, I've published react-query-pages package.
Regarding handling error states - I will try it out, thanks for suggestion!
from query.
@tannerlinsley Thanks Tanner, looking forward to checking it out!
from query.
I like this approach => initialData: initialData && page === 1 ? initialData: undefined
from query.
I like this approach => initialData: initialData && page === 1 ? initialData: undefined
from query.
Related Issues (20)
- onSuccess, onSettled do not work in svelte-query HOT 1
- Errors for shared queryKeys cause all queryFn to run, even if disabled HOT 1
- Handling Persistent Storage Overflow with TanStack Query and persister HOT 2
- Docs: Broken links in react suspense guide HOT 1
- Dependent useQueries creates too many updates (exceeds maximum update depth) HOT 2
- Order matters to useInfiniteQuery options and TS HOT 4
- [vue-query] `useQueries` type inference issues with `select`, `computed` and `Array.map` HOT 1
- enabled: false should not ever fire a query or populate the local store HOT 1
- Solid Query: <Suspense> gets stuck in a loading state HOT 9
- Docs: Broken link on "Installation" page HOT 1
- eslint-plugin-query is incompatible with Eslint 9.x
- Solid Query is mixing up queries during SSR HOT 2
- Docs : Remove ‘$’ from Installation Commands in Documentation
- SSR using NextJS App Router with InitialData Does not Callback HOT 4
- Changing `retryDelay` is ignored for query HOT 2
- Solid Query is failing to recognize signal change in `onMount` HOT 7
- Wrong inferring in useInfiniteQuery HOT 3
- Solid: Cannot access 'refetch' before initialization
- docs: search doesn't work HOT 1
- Docs website doesn't work in Brave HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from query.