T
TanStack•7mo ago
correct-apricot

Cookie based auth review.

Hi all! I just wanted to confirm if my "solution" works fines, break no rules etc. as it's really important for me. I don't want to make mistake that will cost me a lot in the future. If anyone like, please tell me if my approach is legit, alternatively leave your suggestions please! I'll be grateful. main.tsx:
const queryClient = new QueryClient();

const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
scrollRestoration: true,
context: { queryClient: undefined! },
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

const rootElement = document.getElementById("app")!;

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
);
}
const queryClient = new QueryClient();

const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
scrollRestoration: true,
context: { queryClient: undefined! },
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

const rootElement = document.getElementById("app")!;

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
);
}
routes/login/index.tsx:
export const Route = createFileRoute("/login/")({
component: LoginComponent,
beforeLoad: async ({ context }) => {
try {
const data = await context.queryClient.ensureQueryData({
queryKey: ["user"],
queryFn: getUser,
staleTime: Infinity,
});
console.log("data:", data);
if (data) {
throw redirect({
to: "/dashboard",
});
}
} catch (e) {
if (e?.isRedirect) {
throw redirect({
to: "/dashboard",
});
}
}
},
});

function LoginComponent() {
const navigate = Route.useNavigate();
const { loginMutation } = useLogin();

const loginUser = async () => {
loginMutation(
{ email: "foo", password: "bar" },
{
onSuccess: async () => {
await navigate({ to: "/dashboard" });
},
}
);
};

return (
<div className="p-2 grid gap-2 place-items-center">
login page
<button onClick={loginUser}>LOGIN USER</button>
</div>
);
}
export const Route = createFileRoute("/login/")({
component: LoginComponent,
beforeLoad: async ({ context }) => {
try {
const data = await context.queryClient.ensureQueryData({
queryKey: ["user"],
queryFn: getUser,
staleTime: Infinity,
});
console.log("data:", data);
if (data) {
throw redirect({
to: "/dashboard",
});
}
} catch (e) {
if (e?.isRedirect) {
throw redirect({
to: "/dashboard",
});
}
}
},
});

function LoginComponent() {
const navigate = Route.useNavigate();
const { loginMutation } = useLogin();

const loginUser = async () => {
loginMutation(
{ email: "foo", password: "bar" },
{
onSuccess: async () => {
await navigate({ to: "/dashboard" });
},
}
);
};

return (
<div className="p-2 grid gap-2 place-items-center">
login page
<button onClick={loginUser}>LOGIN USER</button>
</div>
);
}
1 Reply
correct-apricot
correct-apricotOP•7mo ago
below protected route: routes/ _authenticated.tsx
export const Route = createFileRoute("/_authenticated")({
beforeLoad: async ({ context, search }) => {
try {
const data = await context.queryClient.ensureQueryData({
queryKey: ["user"],
queryFn: getUser,
staleTime: Infinity,
});

if (!data) {
throw redirect({
to: "/login",
});
}
} catch (e) {
throw redirect({
to: "/login",
});
}
},
});
export const Route = createFileRoute("/_authenticated")({
beforeLoad: async ({ context, search }) => {
try {
const data = await context.queryClient.ensureQueryData({
queryKey: ["user"],
queryFn: getUser,
staleTime: Infinity,
});

if (!data) {
throw redirect({
to: "/login",
});
}
} catch (e) {
throw redirect({
to: "/login",
});
}
},
});
also last file under 'protected group": routes/ _authenticated/dashboard/index.tsx:
export const Route = createFileRoute("/_authenticated/dashboard/")({
component: RouteComponent,
});

function RouteComponent() {
const queryClient = useQueryClient();
const router = useRouter();
const navigate = Route.useNavigate();
const { logoutMutation } = useLogout();

const logOut = async () => {
logoutMutation();
queryClient.setQueryData(["user"], null);
await queryClient.invalidateQueries({ queryKey: ["user"] });

await router.invalidate();

await navigate({ to: "/login" });
};
return (
<div>
Hello "/dashboard/"!
<button onClick={logOut}>LOG OUT</button>
</div>
);
}
export const Route = createFileRoute("/_authenticated/dashboard/")({
component: RouteComponent,
});

function RouteComponent() {
const queryClient = useQueryClient();
const router = useRouter();
const navigate = Route.useNavigate();
const { logoutMutation } = useLogout();

const logOut = async () => {
logoutMutation();
queryClient.setQueryData(["user"], null);
await queryClient.invalidateQueries({ queryKey: ["user"] });

await router.invalidate();

await navigate({ to: "/login" });
};
return (
<div>
Hello "/dashboard/"!
<button onClick={logOut}>LOG OUT</button>
</div>
);
}
I'm curious about few things: - is validating by source of truth as a tanstack-query("user" query) is fine? And my approach in beforeLoad is okay? - am I doing right by setting query to null and invalidate it on logout action?
queryClient.setQueryData(["user"], null);
await queryClient.invalidateQueries({ queryKey: ["user"] });
queryClient.setQueryData(["user"], null);
await queryClient.invalidateQueries({ queryKey: ["user"] });
- Also, I don't like:
catch (e) {
if (e?.isRedirect) {
throw redirect({
to: "/dashboard",
});
}
}
catch (e) {
if (e?.isRedirect) {
throw redirect({
to: "/dashboard",
});
}
}
it's because .then throw redirect so it passed to nearest catch block.. is there any other way to do this? 😄

Did you find this page helpful?