Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build a new useLoadableQuery hook #11349

Closed
jerelmiller opened this issue Nov 8, 2023 · 5 comments · Fixed by #11300
Closed

Build a new useLoadableQuery hook #11349

jerelmiller opened this issue Nov 8, 2023 · 5 comments · Fixed by #11300
Assignees
Milestone

Comments

@jerelmiller
Copy link
Member

jerelmiller commented Nov 8, 2023

This is an umbrella issue to capture the work proposed for the new useLoadableQuery hook that we will be introducing in v3.9.

Purpose

This hook will work similarly to useBackgroundQuery in that it will return a queryRef that can be passed to useReadQuery to suspend and re-render with cache updates. The biggest difference between this hook and useBackgroundQuery is that it will require a call to a function to trigger the creation/update to the queryRef. This is more suitable when using this style hook in an event handler. This is especially useful when you'd like to start preloading some data based on a user interaction.

Here is the proposed API/design of the hook

function Parent() {
  // `queryRef` is `null` until `loadQuery` is called the first time
  const [queryRef, loadQuery, { refetch, fetchMore, reset }] = useLoadableQuery(query, options)

  return (
    <>
      <button onClick={() => loadQuery(variables)}>Load query</button>
      <button onClick={() => reset()}>Reset query</button>
      <Suspense fallback={<SuspenseFallback />}>
        {queryRef && <Child queryRef={queryRef} />}
      </Suspense>
    </>
  );
}

function Child({ queryRef }) {
  const { data } = useReadQuery(queryRef)

  // ...
}

Advantages over useBackgroundQuery

While a similar result can be achieved with useBackgroundQuery, the ergonomics are a bit clunky due to the fact that it would require you to bring in React state to trigger updates to the hook.

As an example, say you have a todo list and would like to start preloading a todo item's details when hovering over its title. With useBackgroundQuery, you'd need a combination of skipToken and React state to achieve this.

const TODO_DETAILS_QUERY = gql``

function TodoList({ todos }) {
  const [preloadedTodoId, setPreloadedTodoId] = useState();
  const [queryRef] = useBackgroundQuery(
    TODO_DETAILS_QUERY, 
    preloadedTodoId ? { variables: { id: preloadedTodoId } } : skipToken
  );

  return todos.map((todo) => (
    <div 
      key={todo.id} 
      onMouseOver={() => setPreloadedTodoId(todo.id)}
    >
      {todo.title}
    </div>
  ));
}

With useLoadableQuery, this becomes much simplier:

const TODO_DETAILS_QUERY = gql``

function TodoList({ todos }) {
  const [queryRef, loadQuery] = useLoadableQuery(TODO_DETAILS_QUERY);

  return todos.map((todo) => (
    <div 
      key={todo.id} 
      onMouseOver={() => loadQuery({ id: todo.id })}
    >
      {todo.title}
    </div>
  ));
}

Naming

I'm opting to avoid calling this useLazyBackgroundQuery primarily because I don't want users to think this works identically to useLazyQuery. While the use cases are similar, there are two biggest differences between the way useLazyQuery and useLoadableQuery works:

  • variables are not passed to the useLoadableQuery hook directly. They can only be set by the loadQuery function returned from the hook.
  • The loadQuery function returns void and does not yield any data itself. All data is accessed through the queryRef by useReadQuery

I opted to name this hook in a different in a way that doesn't imply it works similar to useLazyQuery. I felt naming it useLazyBackgroundQuery might imply similar mechanics, such as the way useSuspenseQuery is designed relative to useQuery.

@jerelmiller jerelmiller added this to the Release 3.9 milestone Nov 8, 2023
@jerelmiller jerelmiller linked a pull request Nov 10, 2023 that will close this issue
@jerelmiller jerelmiller changed the title Build a new useInteractiveQuery hook Build a new useLoadableQuery hook Nov 10, 2023
@jerelmiller jerelmiller self-assigned this Nov 21, 2023
@LydiaF
Copy link

LydiaF commented Nov 23, 2023

Dear Jerel,

This is fantastic, thank you so much.

Would it be possible, if it made sense (I am not sure I am writing good code :)), to have a reset function returned alongside refetch and fetchMore that could set the queryRef back to null? The use case is a search field within a pagination component.

Best,

Lydia

@jerelmiller
Copy link
Member Author

Hey @LydiaF 👋

Glad to hear you have interest in this hook!

The reset function was actually something I was planning on adding to this hook, I just happened to mention this as a comment in #11300 instead of updating this issue 😆.

I've gone ahead and updated this issue description to include the reset function. Thanks for pointing that out!

@LydiaF
Copy link

LydiaF commented Nov 28, 2023

Woops missed that!

But brilliant, thank you so much, looking forward to it!

@jerelmiller
Copy link
Member Author

Finished in #11300 and will be released in 3.9.0-alpha.5 in the next few days. Closing this issue as completed.

Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants