Git Product home page Git Product logo

Comments (15)

tannerlinsley avatar tannerlinsley commented on May 14, 2024 5

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.

tannerlinsley avatar tannerlinsley commented on May 14, 2024 1

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.

cherniavskii avatar cherniavskii commented on May 14, 2024 1

@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?

https://github.com/tannerlinsley/react-query/blob/07b0a66f39a9ce3b5b61ec720f3aebae6128bce2/src/index.js#L351-L361

from query.

cherniavskii avatar cherniavskii commented on May 14, 2024

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:

  1. Add an option to useQuery, which will do the same usePageQuery does, but within same query key string.
  2. Add usePageQuery hook to react-query package. Although the name should be more generic (something like usePrefilledQuery?)
  3. Document it, add example project and keep it as userland solution

@tannerlinsley what do you think?

from query.

JaceHensley avatar JaceHensley commented on May 14, 2024

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.

JaceHensley avatar JaceHensley commented on May 14, 2024

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.

cherniavskii avatar cherniavskii commented on May 14, 2024

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.

JaceHensley avatar JaceHensley commented on May 14, 2024

Yeah that's true, that's kinda why I do it the way in my first comment

from query.

cherniavskii avatar cherniavskii commented on May 14, 2024

@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.

JaceHensley avatar JaceHensley commented on May 14, 2024

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.

nikoskleidis avatar nikoskleidis commented on May 14, 2024

@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.

cherniavskii avatar cherniavskii commented on May 14, 2024

@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.

cherniavskii avatar cherniavskii commented on May 14, 2024

@tannerlinsley Thanks Tanner, looking forward to checking it out!

from query.

azamatos avatar azamatos commented on May 14, 2024

I like this approach => initialData: initialData && page === 1 ? initialData: undefined

from query.

azamatos avatar azamatos commented on May 14, 2024

I like this approach => initialData: initialData && page === 1 ? initialData: undefined

from query.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.