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

feat(suspense): return tuple for useSuspenseQuery and useSuspenseInfiniteQuery #5822

Closed
wants to merge 3 commits into from

Conversation

TkDodo
Copy link
Collaborator

@TkDodo TkDodo commented Aug 1, 2023

No description provided.

@vercel
Copy link

vercel bot commented Aug 1, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Ignored Deployment
Name Status Preview Comments Updated (UTC)
query ⬜️ Ignored (Inspect) Visit Preview Aug 2, 2023 7:23am

@nx-cloud
Copy link

nx-cloud bot commented Aug 1, 2023

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 2c36aec. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 1 target

Sent with 💌 from NxCloud.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Aug 1, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 2c36aec:

Sandbox Source
@tanstack/query-example-react-basic-typescript Configuration
@tanstack/query-example-solid-basic-typescript Configuration
@tanstack/query-example-vue-basic Configuration

@@ -44,4 +44,6 @@ export function useSuspenseInfiniteQuery<
InfiniteQueryObserver as typeof QueryObserver,
queryClient,
) as InfiniteQueryObserverSuccessResult<TData, TError>

return [query.data, query]
Copy link
Contributor

@manudeli manudeli Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to make library user can rename guaranteed TData easily by using tuple returning like useState?
but I want to mention there is some trade offs.

  1. making difference of interface with useQuery will raise barrier to entry a bit for useSuspenseQuery user.
  2. it is expected to cause a BREAKING CHANGE if we add the enabled option in minor version so that TData cannot be guaranteed.
Suggested change
return [query.data, query]
return query

Could I know why you want to make this change more?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is expected to cause a BREAKING CHANGE if we add the enabled option in minor version so that TData cannot be guaranteed.

not breaking if introduced enabled is true by default (and thus data should always be defined)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we add enabled (which we likely won't), we would do so that not specifying enabled will still result in guaranteed TData, and only if you add it (and make it false), it will be undefined. We do the same with initialData.

This change is mainly about developer ergonomics. With a normal query, you usually want access to data, error, isLoading etc. because you need to handle those states. With useSuspenseQuery, you mainly want access to data, and not the rest of the query.

The entry barrier should be fine, as this is a new hook. Also, TypeScript helps :)

Copy link
Contributor

@manudeli manudeli Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is expected to cause a BREAKING CHANGE if we add the enabled option in minor version so that TData cannot be guaranteed.

not breaking if introduced enabled is true by default (and thus data should always be defined)

first of all, I don't think enabled option has been determined to be always true yet. if then, I think we shouldn't make returning type as tuple with hugging chance to make BREAKING CHANGE If enabled can accept a boolean

Also we also have below way that useQuery users are familiar with already too. Isn't it better If we want to access data directly?

const {data: post, ...others} = useSuspenseQuery({
  queryKey: ['post', postId],
  queryFn: () => fetchPost(postId),
})

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make BREAKING CHANGE If enabled can accept a boolean

not sure what enabled has to do here:

const [data] = useSuspenseQuery({
  queryKey: ['foo'],
  queryFn: () => Promise.resolve('foo')
})

data will be of type string here, while:

const [data] = useSuspenseQuery({
  queryKey: ['foo'],
  queryFn: () => Promise.resolve('foo'),
  enabled: someCondition
})

data will be of type string | undefined


Also we also have below way that useQuery users are familiar with already too.

Implementation detail, but object spread has the disadvantage that it triggers getters on the result object, which interferes with tracked queries, so I usually advise against that: https://tkdodo.eu/blog/react-query-render-optimizations#tracked-queries

Copy link
Contributor

@manudeli manudeli Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I worried about same point. if we make enabled option making ReturnType<typeof useSuspenseQuery>[0] can have type TData | undefined

image image

All this cases cannot have advantage of isSuccess flag's type narrowing too.
post: string | undefined

but in this below case
image

we can have advantage of isSuccess flag's type narrowing
post: string

Copy link
Contributor

@manudeli manudeli Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juliusmarminge anyway, I am a big fan of trpc! I am so glad to meet you. I know https://trpc.io/docs/client/react/suspense this one. but I want to keep useSuspenseQuery's return type as just object like useQuery's returning type.

and you can also check situation when we make a @tanstack/react-query's new feature, useSuspenseQuery accepting enabled option in useSuspenseQuery of @suspensive/react-query. so I just don't want to make this useSuspenseQuery meet BREAKING CHANGE because of type narrowing problem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isSuccess check is a good point. However, I really doubt that we will add enabled to useSuspenseQuery. enabled has two use-cases:

  1. dependent queries. With suspense, queries are always run in serial, because react suspends on the first query, then runs and continues on the second query. so our dependent queries example literally becomes:
// Get the user
const [user] = useSuspenseQuery({
  queryKey: ['user', email],
  queryFn: getUserByEmail,
})

const userId = user.id

// Then get the user's projects
const [projects] = useQuery({
  queryKey: ['projects', userId],
  queryFn: getProjectsByUser,
})

there is no enabled needed at all for this.

  1. disabling a query from running until a condition is met, like user-input

suspense evolves a lot component composition, so the lazy queries example could become:

function Todos() {
  const [filter, setFilter] = React.useState('')

  return (
      <div>
        // 🚀 applying the filter will enable and execute the query
        <FiltersForm onApply={setFilter} />
        {filter && <TodosTable filter={filter} />
      </div>
  )
}

function TodosTable({ filter }) {
    const [data] = useSuspenseQuery({
      queryKey: ['todos', filter],
      queryFn: () => fetchTodos(filter),
  })
}

Copy link
Contributor

@manudeli manudeli Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood why you don't care enabled. but yet I don't think returning tuple is good way to give better DX.

  1. Quite different interface with useQuery. Should we make useQuery return tuple too? It will make definitely type narrowing problem
  2. Alternative way is quite good too. (Are there any downsides to these ways?)

Alternative way is also good to rename it too.

it's same with useQuery. it's easy. what do tuple have good thing more?

const postQuery = useSuspenseQuery({
  queryKey: ['post', postId],
  queryFn: () => fetchPost(postId),
})

const post = postQuery.data // TData
const {data: post} = useSuspenseQuery({
  queryKey: ['post', postId],
  queryFn: () => fetchPost(postId),
})

post // TData

We can keep simmilar shape with useQuery

const postQuery = useQuery({
  queryKey: ['post', postId],
  queryFn: () => fetchPost(postId),
})

if(postQuery.isSuccess){
  const post = postQuery.data // TData
}

I think it's easier to have a similar shape if it has similar functions.

Are there any downsides of object returning way?

@codecov-commenter
Copy link

codecov-commenter commented Aug 1, 2023

Codecov Report

Patch coverage has no change and project coverage change: -4.97% ⚠️

Comparison is base (e0aad73) 97.11% compared to head (2c36aec) 92.14%.
Report is 644 commits behind head on beta.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@            Coverage Diff             @@
##             beta    #5822      +/-   ##
==========================================
- Coverage   97.11%   92.14%   -4.97%     
==========================================
  Files          50       19      -31     
  Lines        2391      242    -2149     
  Branches      706       53     -653     
==========================================
- Hits         2322      223    -2099     
+ Misses         67       19      -48     
+ Partials        2        0       -2     

see 69 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@juliusmarminge juliusmarminge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

@TkDodo
Copy link
Collaborator Author

TkDodo commented Aug 4, 2023

I've decided to not move forward with this, because the equivalent changes to useSuspenseQuery would be too hard to achieve right now. It's better to have 3 suspense hooks that have the same interface.

@TkDodo TkDodo closed this Aug 4, 2023
@TkDodo TkDodo deleted the feature/suspense-query-tuple branch August 4, 2023 07:46
@manudeli
Copy link
Contributor

manudeli commented Aug 4, 2023

I've decided to not move forward with this, because the equivalent changes to useSuspenseQuery would be too hard to achieve right now. It's better to have 3 suspense hooks that have the same interface.

This change is not merged now, but I think it was a good idea. Thanks for reconsidering👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants