Skip to content

Feature request: Use generator functions for dependent queries #100

@NatoBoram

Description

@NatoBoram

I'm working on a page with 2 dependent queries. One depends on a page parameter and one depends on another query. If I roughly follow the tutorial, I get this:

// Root profile type
const rootProfileTypeQuery = useQuery(
  `profile-type:${PUBLIC_ROOT_PROFILE_TYPE_ID}`,
  () => getProfileType(PUBLIC_ROOT_PROFILE_TYPE_ID)
)
$: rootProfileType = $rootProfileTypeQuery.data

// Profile
$: id = $page.params["id"]
const profileQuery = useQuery(`profile:${id}`, () => getProfile(id as string), {
  enabled: !!id,
})
$: profileQuery.setOptions(`profile:${id}`, () => getProfile(id as string), {
  enabled: !!id,
})
$: profile = $profileQuery?.data

// Profile type
const profileTypeQuery = useQuery(
  `profile-type:${profile?.profile_type.id}`,
  () => getProfileType(profile?.profile_type.id as string),
  { enabled: !!profile }
)
$: profileTypeQuery.setOptions(
  `profile-type:${profile?.profile_type.id}`,
  () => getProfileType(profile?.profile_type.id as string),
  { enabled: !!profile }
)
$: profileType = $profileTypeQuery?.data

There's an apparent problem: I have to re-type the entire thing every time something changes.

One could just re-create the entire query reactively, but it doesn't feel right. There must be a reason for the enabled parameter to exist.

// Root profile type
const rootProfileTypeQuery = useQuery(
  `profile-type:${PUBLIC_ROOT_PROFILE_TYPE_ID}`,
  () => getProfileType(PUBLIC_ROOT_PROFILE_TYPE_ID)
)
$: rootProfileType = $rootProfileTypeQuery.data

// Profile
$: id = $page.params["id"]
$: profileQuery = useQuery(`profile:${id}`, () => getProfile(id as string), {
  enabled: !!id,
})
$: profile = $profileQuery?.data

// Profile type
$: profileTypeQuery = useQuery(
  `profile-type:${profile?.profile_type.id}`,
  () => getProfileType(profile?.profile_type.id as string),
  { enabled: !!profile }
)
$: profileType = $profileTypeQuery?.data

Working with this doesn't feel good and I'm trying to find solutions to tap in SvelteQuery's great features but with an API that's a bit less insane.

What if it was generated on the fly?

When a query is created, we know what we want to send it even if we don't have it yet. Example:

// Profile type
const profileTypeQuery = useQuery(
	`profile-type:${profile?.profile_type.id}`,
	() => getProfileType(profile?.profile_type.id as string),
	{ enabled: !!profile },
)
$: profileTypeQuery.setOptions(
	`profile-type:${profile?.profile_type.id}`,
	() => getProfileType(profile?.profile_type.id as string),
	{ enabled: !!profile },
)
$: profileType = $profileTypeQuery?.data
  • I know that the ID of the query has to receive a part of profile.
  • I know that the fetch has to receive a part of profile
  • I know that the query mustn't be enabled unless I have profile

Why not use generator functions to generate the ID and the fetch only when enabled is turned on?

// Profile type
const profileTypeQuery = generateUseQuery(
	profile,
	profile => `profile-type:${profile?.profile_type.id}`,
	profile => getProfileType(profile?.profile_type.id),
	profile => ({ enabled: !!profile }),
)
$: profileTypeQuery.setInput(profile)
$: profileType = $profileTypeQuery?.data

This isn't necessarily shorter than the other ones, but it's definitely cleaner and it can be put inside a function somewhere and imported in a component.

// api.ts
export function queryProfileType(profile?: Profile) {
	return generateUseQuery(
		profile,
		profile => `profile-type:${profile?.profile_type.id}`,
		profile => getProfileType(profile?.profile_type.id),
		profile => ({ enabled: !!profile }),
	)
}

// +page.svelte
const profileTypeQuery = queryProfileType(profile)
$: profileTypeQuery.setInput(profile)
$: profileType = $profileTypeQuery?.data

And now we can use Svelte Query in 2 lines from a component's perspective. Doing this with the current API requires quite a lot of boilerplate that could be saved by generator functions.

// api.ts
function queryProfileType(id?: string) {
	return useQuery(`profile-type:${id}`, () => getProfileType(id as string), { enabled: !!id })
}

function setOptionsProfileType(
	query: UseQueryStoreResult<ProfileType, unknown, ProfileType, `profile-type:${string}`>,
	id?: string,
) {
	return query.setOptions(`profile-type:${id}`, () => getProfileType(id ?? ''), {
		enabled: !!profile,
	})
}

// Profile type
const profileTypeQuery = queryProfileType(profile?.id)
$: setOptionsProfileType(profileTypeQuery, profile?.profile_type.id)
$: profileType = $profileTypeQuery?.data

One could make generator functions to avoid duplicating code in api.ts but it's even more boilerplate for something that should be simple.

function queryId(id?: string): `profile-type:${string}` {
	return `profile-type:${id}`
}

function queryFetch(id?: string) {
	return () => getProfileType(id ?? '')
}

function queryOptions(id?: string) {
	return { enabled: !!id }
}

export function queryProfileType(id?: string) {
	return useQuery(queryId(id), queryFetch(id), queryOptions(id))
}

export function setQueryProfileType(
	query: UseQueryStoreResult<ProfileType, unknown, ProfileType, `profile-type:${string}`>,
	id?: string,
) {
	return query.setOptions(queryId(id), queryFetch(id), queryOptions(id))
}

// All of this to be able to do
const profileTypeQuery = queryProfileType(profile?.profile_type.id)
$: setQueryProfileType(profileTypeQuery, profile?.profile_type.id)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions