T
TanStack15mo ago
frail-apricot

How to use `useSuspenseQuery` to ensure data is always there?

Hey all. I'm working on an app where basically each screen is bound to be accessed if the user is in an account. I want to develop a hook that will provide the account data and avoid doing undefined checks. Right now, I grad the account id from the url, pass it to a query where and use enabled to decide if the query should be called. It is called each time but the undefined checks are annoying. Another approach is to retun null early if (!accountId), which is also annoying. Here is what I have so far:
import { useSearchParams } from 'react-router-dom';

import type { IAccount } from '@payhawk/public-api-contracts';

import {
useGetAccountSuspense,
useGetUserAccounts,
} from '@data';

export const useAccount = (): { account: IAccount; accountId: string } => {
const [params, setParam] = useSearchParams();

const accountId = params.get('account') ?? undefined;

const userAccounts = useGetUserAccounts();
const account = useGetAccountSuspense(accountId);

// if no account id in path, navigate to the first account from all possible accounts
if (!accountId && !account.isLoading && !account) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
setParam(current => {
current.set('account', firstAccount.id);
return current;
});
}
}

return { account: account.data };
};

export const useGetAccountSuspense = (accountId?: string) => {
return useSuspenseQuery({
queryKey: ['accounts', accountId],
queryFn: () => fetch(`/accounts/${accountId ?? ''}`);
});
};
import { useSearchParams } from 'react-router-dom';

import type { IAccount } from '@payhawk/public-api-contracts';

import {
useGetAccountSuspense,
useGetUserAccounts,
} from '@data';

export const useAccount = (): { account: IAccount; accountId: string } => {
const [params, setParam] = useSearchParams();

const accountId = params.get('account') ?? undefined;

const userAccounts = useGetUserAccounts();
const account = useGetAccountSuspense(accountId);

// if no account id in path, navigate to the first account from all possible accounts
if (!accountId && !account.isLoading && !account) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
setParam(current => {
current.set('account', firstAccount.id);
return current;
});
}
}

return { account: account.data };
};

export const useGetAccountSuspense = (accountId?: string) => {
return useSuspenseQuery({
queryKey: ['accounts', accountId],
queryFn: () => fetch(`/accounts/${accountId ?? ''}`);
});
};
The thing is, params.get('account') might not be defined, in which case I want to redirect the user to one of their accounts. My problem is that useSuspenseQuery does not allow for enabled and I have to do accountId ?? '' which causes an extra call to be made. I feel like this is a common issue, just not sure how to get around it. Thanks!
1 Reply
frail-apricot
frail-apricotOP15mo ago
export const useGetAccountSuspense = (accountId?: string) => {
let _id = accountId;
const [, setParam] = useSearchParams();

const userAccounts = useGetUserAccountsSuspense();

const existingAccountIds = new Set(userAccounts.data?.map(account => account.id));

if (!userAccounts.isLoading && accountId && !existingAccountIds.has(accountId)) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
setParam(current => {
current.set('account', firstAccount.id);
return current;
});

_id = firstAccount.id;
}
}

if (!_id) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
_id = firstAccount.id;
setParam(current => {
current.set('account', firstAccount.id);
return current;
});
}
}

return useSuspenseQuery({
queryKey: ['accounts', accountId],
queryFn: async () => {
if (!accountId) {
return null;
}

return apiClient.get<IAccount>(`/accounts/${encodeURIComponent(_id!)}`).then(response => response.data);
},
});
};
export const useGetAccountSuspense = (accountId?: string) => {
let _id = accountId;
const [, setParam] = useSearchParams();

const userAccounts = useGetUserAccountsSuspense();

const existingAccountIds = new Set(userAccounts.data?.map(account => account.id));

if (!userAccounts.isLoading && accountId && !existingAccountIds.has(accountId)) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
setParam(current => {
current.set('account', firstAccount.id);
return current;
});

_id = firstAccount.id;
}
}

if (!_id) {
const firstAccount = userAccounts.data?.[0];

if (firstAccount) {
_id = firstAccount.id;
setParam(current => {
current.set('account', firstAccount.id);
return current;
});
}
}

return useSuspenseQuery({
queryKey: ['accounts', accountId],
queryFn: async () => {
if (!accountId) {
return null;
}

return apiClient.get<IAccount>(`/accounts/${encodeURIComponent(_id!)}`).then(response => response.data);
},
});
};
This is how it's going but I like it even less than then previous one...

Did you find this page helpful?