T
TanStack•16mo ago
flat-fuchsia

Why can modules.data be undefined here?

export const Route = createFileRoute('/_auth/gameserver/$gameServerId/modules')({
loader: async ({ params, context }) => {
const [modules, moduleInstallations] = await Promise.all([
context.queryClient.ensureQueryData(modulesQueryOptions()),
context.queryClient.ensureQueryData(gameServerModuleInstallationsOptions(params.gameServerId)),
]);
return { modules: modules, installations: moduleInstallations };
},
component: Component,
pendingComponent: () => (
<CardList>
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={i} variant="rectangular" height="100%" width="100%" />
))}
</CardList>
),
});

export function Component() {
const loaderData = Route.useLoaderData();
const { gameServerId } = Route.useParams();

const { data: installations } = useSuspenseQuery({
...gameServerModuleInstallationsOptions(gameServerId),
initialData: loaderData.installations,
});
const { data: modules } = useSuspenseQuery({
...modulesQueryOptions(),
initialData: loaderData.modules,
});

const mappedModules = modules.data.map((mod) => {
...
});
export const Route = createFileRoute('/_auth/gameserver/$gameServerId/modules')({
loader: async ({ params, context }) => {
const [modules, moduleInstallations] = await Promise.all([
context.queryClient.ensureQueryData(modulesQueryOptions()),
context.queryClient.ensureQueryData(gameServerModuleInstallationsOptions(params.gameServerId)),
]);
return { modules: modules, installations: moduleInstallations };
},
component: Component,
pendingComponent: () => (
<CardList>
{Array.from({ length: 10 }).map((_, i) => (
<Skeleton key={i} variant="rectangular" height="100%" width="100%" />
))}
</CardList>
),
});

export function Component() {
const loaderData = Route.useLoaderData();
const { gameServerId } = Route.useParams();

const { data: installations } = useSuspenseQuery({
...gameServerModuleInstallationsOptions(gameServerId),
initialData: loaderData.installations,
});
const { data: modules } = useSuspenseQuery({
...modulesQueryOptions(),
initialData: loaderData.modules,
});

const mappedModules = modules.data.map((mod) => {
...
});
5 Replies
flat-fuchsia
flat-fuchsiaOP•16mo ago
I don't understand how modules.data can be undefined. I provide initialData coming from the loaders.
harsh-harlequin
harsh-harlequin•16mo ago
With suspense, you don't need i initialData The only way it can be undefined is if the queryFn returns undefined (which it shouldn't do)
flat-fuchsia
flat-fuchsiaOP•16mo ago
if I would use useQuery instead, since I use initial data coming from the loader. It should still not be able to be undefined right?
harsh-harlequin
harsh-harlequin•16mo ago
with useQuery, you need initialData. With suspense, you don't. please share your queryFn because I think the issue must be in there.
flat-fuchsia
flat-fuchsiaOP•16mo ago
export const modulesQueryOptions = (queryParams: ModuleSearchInputDTO = {}) =>
queryOptions<ModuleOutputArrayDTOAPI, AxiosError<ModuleOutputArrayDTOAPI>>({
queryKey: [...moduleKeys.list(), ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().module.moduleControllerSearch(queryParams)).data,
});

// Usecase: gets rid of empty objects in the queryKeys
// spreading an empty object returns an empty object
// Spreading an empty array returns nothing
export function queryParamsToArray(queryParams: any) {
return Object.keys(queryParams).length > 0 ? [{ ...queryParams }] : [];
}
export const modulesQueryOptions = (queryParams: ModuleSearchInputDTO = {}) =>
queryOptions<ModuleOutputArrayDTOAPI, AxiosError<ModuleOutputArrayDTOAPI>>({
queryKey: [...moduleKeys.list(), ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().module.moduleControllerSearch(queryParams)).data,
});

// Usecase: gets rid of empty objects in the queryKeys
// spreading an empty object returns an empty object
// Spreading an empty array returns nothing
export function queryParamsToArray(queryParams: any) {
return Object.keys(queryParams).length > 0 ? [{ ...queryParams }] : [];
}
I have similar issues with infiniteQuery where pages can be undefined
export const Route = createFileRoute('/_auth/_global/modules')({
beforeLoad: ({ context }) => {
if (!hasPermission(context.auth.session, ['READ_MODULES'])) {
throw redirect({ to: '/forbidden' });
}
},
loader: async ({ context }) => {
const opts = modulesInfiniteQueryOptions();
const modules =
context.queryClient.getQueryData(opts.queryKey) ?? (await context.queryClient.fetchInfiniteQuery(opts));
return modules;
},
component: Component,
pendingComponent: PendingComponent,
});

function PendingComponent() {
return <Skeleton variant="rectangular" height="100%" width="100%" />;
}

function Component() {
useDocumentTitle('Modules');
const navigate = useNavigate();
const theme = useTheme();
const loaderModules = Route.useLoaderData();

const {
data: modules,
isLoading,
isFetching,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
...modulesInfiniteQueryOptions(),
initialData: loaderModules,
});

const flattenedModules = modules.pages.flatMap((page) => page.data);
...

export const modulesInfiniteQueryOptions = (queryParams: ModuleSearchInputDTO = {}) =>
infiniteQueryOptions<ModuleOutputArrayDTOAPI, AxiosError<ModuleOutputArrayDTOAPI>>({
queryKey: [...moduleKeys.list(), ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().module.moduleControllerSearch(queryParams)).data,
initialPageParam: 0,
getNextPageParam: (lastPage) => hasNextPage(lastPage.meta),
});
export const Route = createFileRoute('/_auth/_global/modules')({
beforeLoad: ({ context }) => {
if (!hasPermission(context.auth.session, ['READ_MODULES'])) {
throw redirect({ to: '/forbidden' });
}
},
loader: async ({ context }) => {
const opts = modulesInfiniteQueryOptions();
const modules =
context.queryClient.getQueryData(opts.queryKey) ?? (await context.queryClient.fetchInfiniteQuery(opts));
return modules;
},
component: Component,
pendingComponent: PendingComponent,
});

function PendingComponent() {
return <Skeleton variant="rectangular" height="100%" width="100%" />;
}

function Component() {
useDocumentTitle('Modules');
const navigate = useNavigate();
const theme = useTheme();
const loaderModules = Route.useLoaderData();

const {
data: modules,
isLoading,
isFetching,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
...modulesInfiniteQueryOptions(),
initialData: loaderModules,
});

const flattenedModules = modules.pages.flatMap((page) => page.data);
...

export const modulesInfiniteQueryOptions = (queryParams: ModuleSearchInputDTO = {}) =>
infiniteQueryOptions<ModuleOutputArrayDTOAPI, AxiosError<ModuleOutputArrayDTOAPI>>({
queryKey: [...moduleKeys.list(), ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().module.moduleControllerSearch(queryParams)).data,
initialPageParam: 0,
getNextPageParam: (lastPage) => hasNextPage(lastPage.meta),
});
maybe my queryParamsToAray is broken? @TkDodo 🔮 I got rid of all suspenses. but same issue. ahhhh, I think I got it, they have the same keys when queryParamsToArray is empty and when I go to one page, and then to the other one, it uses the cache, but because one is infinite the format is different....

Did you find this page helpful?