T
TanStack2y ago
sunny-green

Page not reloading

The route switches to the new gameserver whenever that route is not cached. When cached it does not switch. I'm on wayland and my screen recording is a bit janky.. I looked at incorrect react-query cache, I see a cache for each gameserver, I see cached matched route for the route that matches the gameserverId It seems like the useParams update is slow? and when I select a new gameserver, the param is not updated, so the navigation is not triggered If someone would have an idea of what's happening here, I'd much appreciate it... keys:
export const installedModuleKeys = {
all: ['installed modules'] as const,
list: (gameServerId: string) => [...installedModuleKeys.all, 'list', gameServerId] as const,
detail: (gameServerId: string, moduleId: string) =>
[...installedModuleKeys.all, 'detail', gameServerId, moduleId] as const,
};
export const installedModuleKeys = {
all: ['installed modules'] as const,
list: (gameServerId: string) => [...installedModuleKeys.all, 'list', gameServerId] as const,
detail: (gameServerId: string, moduleId: string) =>
[...installedModuleKeys.all, 'detail', gameServerId, moduleId] as const,
};
queryOptions:
export const gameServerModuleInstallationsOptions = (gameServerId: string) => {
return queryOptions<ModuleInstallationOutputDTO[], AxiosError<ModuleInstallationOutputDTOAPI>>({
queryKey: installedModuleKeys.list(gameServerId),
queryFn: async () =>
(await getApiClient().gameserver.gameServerControllerGetInstalledModules(gameServerId)).data.data,
});
};
export const gameServerModuleInstallationsOptions = (gameServerId: string) => {
return queryOptions<ModuleInstallationOutputDTO[], AxiosError<ModuleInstallationOutputDTOAPI>>({
queryKey: installedModuleKeys.list(gameServerId),
queryFn: async () =>
(await getApiClient().gameserver.gameServerControllerGetInstalledModules(gameServerId)).data.data,
});
};
No description
5 Replies
sunny-green
sunny-greenOP2y ago
component:
export const Route = createFileRoute('/_auth/gameserver/$gameServerId/modules')({
beforeLoad: ({ context }) => {
if (!hasPermission(context.auth.session, ['READ_MODULES'])) {
throw redirect({ to: '/forbidden' });
}
},
loader: async ({ params, context }) => {
const [modules, moduleInstallations] = await Promise.all([
context.queryClient.ensureQueryData(modulesOptions({})),
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();
//useGameServerDocumentTitle('Modules');

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

const mappedModules = modules.data.map((mod) => {
const installation = installations.find((inst) => inst.moduleId === mod.id);
return {
...mod,
installed: !!installation,
installation: installation,
};
});

const installedModules = mappedModules.filter((mod) => mod.installed);
const availableModules = mappedModules.filter((mod) => !mod.installed);

const theme = useTheme();

return (... );
}
export const Route = createFileRoute('/_auth/gameserver/$gameServerId/modules')({
beforeLoad: ({ context }) => {
if (!hasPermission(context.auth.session, ['READ_MODULES'])) {
throw redirect({ to: '/forbidden' });
}
},
loader: async ({ params, context }) => {
const [modules, moduleInstallations] = await Promise.all([
context.queryClient.ensureQueryData(modulesOptions({})),
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();
//useGameServerDocumentTitle('Modules');

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

const mappedModules = modules.data.map((mod) => {
const installation = installations.find((inst) => inst.moduleId === mod.id);
return {
...mod,
installed: !!installation,
installation: installation,
};
});

const installedModules = mappedModules.filter((mod) => mod.installed);
const availableModules = mappedModules.filter((mod) => !mod.installed);

const theme = useTheme();

return (... );
}
select:
import { FC, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { styled } from '@takaro/lib-components';
import { GameServerSelect } from 'components/selects';
import { useMatchRoute, useNavigate } from '@tanstack/react-router';

export const StyledForm = styled.form`
div {
margin-bottom: 0;
}
`;

type FormFields = { gameServerId: string };

interface GameServerSelectNavProps {
currentSelectedGameServerId: string;
}

export const GlobalGameServerSelect: FC<GameServerSelectNavProps> = ({
currentSelectedGameServerId: selectedGameServerId,
}) => {
const routeMatch = useMatchRoute();
const navigate = useNavigate();
const { control, watch, setValue } = useForm<FormFields>({
mode: 'onChange',
defaultValues: {
gameServerId: selectedGameServerId,
},
});

useEffect(() => {
const subscription = watch(({ gameServerId }) => {
// a new gameserver was selected
if (gameServerId && gameServerId !== selectedGameServerId) {
const match = routeMatch({ to: '/gameserver/$gameServerId', fuzzy: true });
if (match) {
navigate({
to: `/gameserver/$gameServerId/${match['**']}`,
params: { gameServerId },
});
}
}
});
return () => subscription.unsubscribe();
}, [watch('gameServerId'), routeMatch]);

useEffect(() => {
setValue('gameServerId', selectedGameServerId);
}, [selectedGameServerId]);

return <GameServerSelect control={control} name="gameServerId" label="" />;
};
import { FC, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { styled } from '@takaro/lib-components';
import { GameServerSelect } from 'components/selects';
import { useMatchRoute, useNavigate } from '@tanstack/react-router';

export const StyledForm = styled.form`
div {
margin-bottom: 0;
}
`;

type FormFields = { gameServerId: string };

interface GameServerSelectNavProps {
currentSelectedGameServerId: string;
}

export const GlobalGameServerSelect: FC<GameServerSelectNavProps> = ({
currentSelectedGameServerId: selectedGameServerId,
}) => {
const routeMatch = useMatchRoute();
const navigate = useNavigate();
const { control, watch, setValue } = useForm<FormFields>({
mode: 'onChange',
defaultValues: {
gameServerId: selectedGameServerId,
},
});

useEffect(() => {
const subscription = watch(({ gameServerId }) => {
// a new gameserver was selected
if (gameServerId && gameServerId !== selectedGameServerId) {
const match = routeMatch({ to: '/gameserver/$gameServerId', fuzzy: true });
if (match) {
navigate({
to: `/gameserver/$gameServerId/${match['**']}`,
params: { gameServerId },
});
}
}
});
return () => subscription.unsubscribe();
}, [watch('gameServerId'), routeMatch]);

useEffect(() => {
setValue('gameServerId', selectedGameServerId);
}, [selectedGameServerId]);

return <GameServerSelect control={control} name="gameServerId" label="" />;
};
also, not sure if this is an open issue, but my router devtools are extremely small, barely readable, I need to set it on like 200% to see anything
sunny-green
sunny-greenOP2y ago
No description
sunny-green
sunny-greenOP2y ago
anything I can add that would help?
other-emerald
other-emerald2y ago
please provide a minimal complete example on e.g. Stackblitz
sunny-green
sunny-greenOP2y ago
I understand, but I have so many moving parts here.. I kinda hoped that providing parts of the code that are hit would be enough.. I would really appreciate it if someone could have a look

Did you find this page helpful?