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:
queryOptions:
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,
};
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,
});
};

5 Replies
sunny-greenOP•2y ago
component:
select:
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
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 (... );
}
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="" />;
};
sunny-greenOP•2y ago

sunny-greenOP•2y ago
anything I can add that would help?
other-emerald•2y ago
please provide a minimal complete example on e.g. Stackblitz
sunny-greenOP•2y 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