S
SolidJS•2mo ago
MaveriX89

Exception thrown when invoking useNavigate within Router root layout

I am having a real hard time working through this problem. I feel like I've tried everything but nothing has been working and there's probably something I'm not seeing clearly anymore. The short of it is, when I attempt to invoke navigate("/login") from my root layout component, I am greeted with the following exception:
TypeError: Cannot read properties of undefined (reading 'path')
TypeError: Cannot read properties of undefined (reading 'path')
Here is the entirety of my App.tsx file:
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
The area to focus on is the onClick for the Sign Out NavbarLink. In the callback, I am invoking the Better Auth client to sign out and on success, I navigate to /login. It's that invocation that causes the exception. I'm not exactly sure the proper way to debug this and could use all the help I can get
19 Replies
MaveriX89
MaveriX89OP•2mo ago
Just adding more additional context here. The NavbarLink components are just <A> elements from @solidjs/router wrapped under <li> tags. And for reference, here is my Login.tsx:
import { action, createAsync, redirect, useNavigate, useSubmission } from "@solidjs/router";
import { createEffect, Show } from "solid-js";

import { Button } from "~/components/ui/button";
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";

import Logo from "../assets/logo_login.svg";
import { authClient } from "../lib/auth-client";
import { getAuthState } from "../lib/queries";
import { convertToRecord, isNil } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

const authenticate = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

if (response.error) throw new Error(response.error.message);

throw redirect("/dashboard");
});

export default function SignIn() {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());
const authenticating = useSubmission(authenticate);

createEffect(() => {
if (!isNil(auth())) {
navigate("/dashboard");
}
});

return (
<div class="h-full flex flex-col items-center justify-center gap-6">
<img src={Logo} width="200" />
<form action={authenticate} method="post" class="flex flex-col gap-6 w-[300px]">
<TextField>
<TextFieldLabel class="mb-2">Username</TextFieldLabel>
<TextFieldInput name="username" autocomplete="username" />
</TextField>
<TextField>
<TextFieldLabel class="mb-2">Password</TextFieldLabel>
<TextFieldInput name="password" type="password" autocomplete="current-password" />
</TextField>
<Button class="button" type="submit" disabled={authenticating.pending}>
{authenticating.pending ? "Authenticating..." : "Sign In"}
</Button>
</form>
<Show when={authenticating.error}>
<p style={{ color: "red" }} role="alert" id="error-message">
{authenticating.error?.message}
</p>
</Show>
</div>
);
}
import { action, createAsync, redirect, useNavigate, useSubmission } from "@solidjs/router";
import { createEffect, Show } from "solid-js";

import { Button } from "~/components/ui/button";
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";

import Logo from "../assets/logo_login.svg";
import { authClient } from "../lib/auth-client";
import { getAuthState } from "../lib/queries";
import { convertToRecord, isNil } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

const authenticate = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

if (response.error) throw new Error(response.error.message);

throw redirect("/dashboard");
});

export default function SignIn() {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());
const authenticating = useSubmission(authenticate);

createEffect(() => {
if (!isNil(auth())) {
navigate("/dashboard");
}
});

return (
<div class="h-full flex flex-col items-center justify-center gap-6">
<img src={Logo} width="200" />
<form action={authenticate} method="post" class="flex flex-col gap-6 w-[300px]">
<TextField>
<TextFieldLabel class="mb-2">Username</TextFieldLabel>
<TextFieldInput name="username" autocomplete="username" />
</TextField>
<TextField>
<TextFieldLabel class="mb-2">Password</TextFieldLabel>
<TextFieldInput name="password" type="password" autocomplete="current-password" />
</TextField>
<Button class="button" type="submit" disabled={authenticating.pending}>
{authenticating.pending ? "Authenticating..." : "Sign In"}
</Button>
</form>
<Show when={authenticating.error}>
<p style={{ color: "red" }} role="alert" id="error-message">
{authenticating.error?.message}
</p>
</Show>
</div>
);
}
I make heavy use of the getAuthState query which you can see here:
import { query } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

return isNil(session) || isNil(session.data)
? null
: {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
import { query } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

return isNil(session) || isNil(session.data)
? null
: {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
From the looks of it, I do see the url change in my app showing that it went to /login -- however, everytime it does, the <ErrorBoundary> fallback component kicks in and displays instead of the Login page I defined above due to that thrown exception.
Madaxen86
Madaxen86•2mo ago
Maybe moving the RootLayout to a different file and importing in app.tsx helps?
peerreynders
peerreynders•2mo ago
To me this
() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate('/login');
},
},
});
};
() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate('/login');
},
},
});
};
has an uncanny similarity to your previous issue https://discord.com/channels/722131463138705510/1331326621629812766 so why not give something like
async () => {
const signedOut = await new Promise<boolean>((resolve) => {
authClient.signOut({
fetchOptions: {
onSuccess: () => resolve(true),
onError: () => resolve(false),
},
});
});
if (signedOut) navigate('/login');
};
async () => {
const signedOut = await new Promise<boolean>((resolve) => {
authClient.signOut({
fetchOptions: {
onSuccess: () => resolve(true),
onError: () => resolve(false),
},
});
});
if (signedOut) navigate('/login');
};
a try.
peerreynders
peerreynders•2mo ago
Having at the look at the documentation https://www.better-auth.com/docs/basic-usage#signout
await authClient.signOut();
await authClient.signOut();
Note the await. So it should have really been
async () => {
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate('/login');
},
},
});
};
async () => {
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate('/login');
},
},
});
};
all along.
Basic Usage | Better Auth
Getting started with Better Auth
MaveriX89
MaveriX89OP•2mo ago
@peerreynders unfortunately the refactoring to async/await syntax results in the same TypeError exception
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={async () => {
const result = await authClient.signOut();

if (result.error) {
console.error(result.error);
return
}

navigate("/login");
}}
>
Sign Out
</NavbarLink>
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={async () => {
const result = await authClient.signOut();

if (result.error) {
console.error(result.error);
return
}

navigate("/login");
}}
>
Sign Out
</NavbarLink>
Shifting the RootLayout into its own module also had no impact unfortunately @peerreynders also, I get complaints from the Solid ESLint rules around async callbacks:
This tracked scope should not be async. Solid's reactivity only tracks synchronously. eslint(solid/reactivity)
This tracked scope should not be async. Solid's reactivity only tracks synchronously. eslint(solid/reactivity)
That's why I went with the object callback structure from the Better Auth docs around signOut
peerreynders
peerreynders•2mo ago
Sure. The message
TypeError: Cannot read properties of undefined (reading 'path')
TypeError: Cannot read properties of undefined (reading 'path')
would lead me to the hypothesis that perhaps somehow the navigate doesn't seem to have access to the router when it is executed. Frankly at this point does even:
() => {
console.log("LogOut onClick");
navigate("/login");
}
() => {
console.log("LogOut onClick");
navigate("/login");
}
work? As a sanity check, this (as expected) works:
// file: src/app.tsx
import { MetaProvider, Title } from '@solidjs/meta';
import { Router, Route, useNavigate } from '@solidjs/router';
import { Suspense } from 'solid-js';
import { Home } from './routes/index.js';
import { About } from './routes/about.js';
import { NotFound } from './routes/404.js';

import type { ParentProps } from 'solid-js';

function Layout(props: ParentProps) {
const navigate = useNavigate();
const toHome = () => navigate('/');
const toAbout = () => navigate('/about');

return (
<MetaProvider>
<Title>Solid - Router</Title>
<p onClick={toHome}>Index</p>
<p onClick={toAbout}>About</p>
<Suspense>{props.children}</Suspense>
</MetaProvider>
);
}

function App() {
return (
<Router root={Layout}>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="*404" component={NotFound} />
</Router>
);
}

export { App };
// file: src/app.tsx
import { MetaProvider, Title } from '@solidjs/meta';
import { Router, Route, useNavigate } from '@solidjs/router';
import { Suspense } from 'solid-js';
import { Home } from './routes/index.js';
import { About } from './routes/about.js';
import { NotFound } from './routes/404.js';

import type { ParentProps } from 'solid-js';

function Layout(props: ParentProps) {
const navigate = useNavigate();
const toHome = () => navigate('/');
const toAbout = () => navigate('/about');

return (
<MetaProvider>
<Title>Solid - Router</Title>
<p onClick={toHome}>Index</p>
<p onClick={toAbout}>About</p>
<Suspense>{props.children}</Suspense>
</MetaProvider>
);
}

function App() {
return (
<Router root={Layout}>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="*404" component={NotFound} />
</Router>
);
}

export { App };
MaveriX89
MaveriX89OP•2mo ago
@peerreynders I think I may have an idea of what is causing the problem which leads me to a question -- I am using query to cache data regarding my session state. After signing out using Better Auth, I need to revalidate that cache key which my components depend on for session state. Can I use revalidate outside of a query ?
peerreynders
peerreynders•2mo ago
You should be able to as long as you pass it the correct key. https://github.com/solidjs/solid-router/blob/50c5d7bdef6acc5910c6eb35ba6a24b15aae3ef6/src/data/query.ts#L41-L49 revalidate just accesses the module global cacheMap.
GitHub
solid-router/src/data/query.ts at 50c5d7bdef6acc5910c6eb35ba6a24b15...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
MaveriX89
MaveriX89OP•2mo ago
I was away from my computer which was why I asked the above question. Just rushed back to try it out and that did the trick for a simple use-case (I removed my authenticated route protection) but then that finally revealed what I believe is the true cause of my issue Ugh...nvm, I spoke too soon 😭 I thought the problem had to do with my SignIn component here:
import { action, createAsync, redirect, useNavigate, useSubmission } from "@solidjs/router";
import { createEffect, Show } from "solid-js";

import { Button } from "~/components/ui/button";
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";

import Logo from "../assets/logo_login.svg";
import { authClient } from "../lib/auth-client";
import { getAuthState } from "../lib/queries";
import { convertToRecord, isNil } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

const authenticate = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

console.log("RESPONSE >>>", response);
if (response.error) throw new Error(response.error.message);

console.log("THROWING DASHBOARD REDIRECT")
throw redirect("/dashboard");
});

export default function SignIn() {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());
const authenticating = useSubmission(authenticate);

createEffect(() => {
if (!isNil(auth())) {
console.log("Navigating to dashboard...", auth());
navigate("/dashboard");
}
});

return (
<div class="h-full flex flex-col items-center justify-center gap-6">
<img src={Logo} width="200" />
<form action={authenticate} method="post" class="flex flex-col gap-6 w-[300px]">
<TextField>
<TextFieldLabel class="mb-2">Username</TextFieldLabel>
<TextFieldInput name="username" autocomplete="username" />
</TextField>
<TextField>
<TextFieldLabel class="mb-2">Password</TextFieldLabel>
<TextFieldInput name="password" type="password" autocomplete="current-password" />
</TextField>
<Button class="button" type="submit" disabled={authenticating.pending}>
{authenticating.pending ? "Authenticating..." : "Sign In"}
</Button>
</form>
<Show when={authenticating.error}>
<p style={{ color: "red" }} role="alert" id="error-message">
{authenticating.error?.message}
</p>
</Show>
</div>
);
}
import { action, createAsync, redirect, useNavigate, useSubmission } from "@solidjs/router";
import { createEffect, Show } from "solid-js";

import { Button } from "~/components/ui/button";
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";

import Logo from "../assets/logo_login.svg";
import { authClient } from "../lib/auth-client";
import { getAuthState } from "../lib/queries";
import { convertToRecord, isNil } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

const authenticate = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

console.log("RESPONSE >>>", response);
if (response.error) throw new Error(response.error.message);

console.log("THROWING DASHBOARD REDIRECT")
throw redirect("/dashboard");
});

export default function SignIn() {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());
const authenticating = useSubmission(authenticate);

createEffect(() => {
if (!isNil(auth())) {
console.log("Navigating to dashboard...", auth());
navigate("/dashboard");
}
});

return (
<div class="h-full flex flex-col items-center justify-center gap-6">
<img src={Logo} width="200" />
<form action={authenticate} method="post" class="flex flex-col gap-6 w-[300px]">
<TextField>
<TextFieldLabel class="mb-2">Username</TextFieldLabel>
<TextFieldInput name="username" autocomplete="username" />
</TextField>
<TextField>
<TextFieldLabel class="mb-2">Password</TextFieldLabel>
<TextFieldInput name="password" type="password" autocomplete="current-password" />
</TextField>
<Button class="button" type="submit" disabled={authenticating.pending}>
{authenticating.pending ? "Authenticating..." : "Sign In"}
</Button>
</form>
<Show when={authenticating.error}>
<p style={{ color: "red" }} role="alert" id="error-message">
{authenticating.error?.message}
</p>
</Show>
</div>
);
}
But I think the issue is some combination of the navigate calls here and what I am doing to implement "protected" routes in my app here with the Router:
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
My ActiveUserSession component is here:
const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};
const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};
What I see is that the navigate post sign out does occur and my URL reflects the login redirect but that is where I hit the error boundary fallback view. I was thinking that some navigate was occurring to a protected route that was no longer mounted since the user session is over but none of the console logs I place get triggered and I don't see a navigate called anywhere else post the login redirect...
peerreynders
peerreynders•2mo ago
side thought: could you eliminate ActiveUserSession if in
const auth = createAsync(() => getAuthState());
const auth = createAsync(() => getAuthState());
getAuthState() just throw redirect('/login') when there is no authentication?
MaveriX89
MaveriX89OP•2mo ago
You referring to the actual implementation here?
import { query } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

return isNil(session) || isNil(session.data)
? null
: {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
import { query } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

return isNil(session) || isNil(session.data)
? null
: {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
peerreynders
peerreynders•2mo ago
Yes, either return a user record OR throw a redirect to /login. In the case of strello it doesn't seem to interfere with the /login route
GitHub
strello/src/lib/index.ts at 9c9ae973d96cc045914e696757a1b5f31efc6fa...
Contribute to solidjs-community/strello development by creating an account on GitHub.
MaveriX89
MaveriX89OP•2mo ago
Just did the following and still the same result -- also removed the navigate("/login") on the success handler for signOut and just relying on revalidate(getAuthState.key) to trigger that new throw in the query 😭
import { query, redirect } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

if (isNil(session) || isNil(session.data)) {
throw redirect("/login");
}

return {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
import { query, redirect } from "@solidjs/router";

import { authClient } from "./auth-client";
import { isNil } from "./utils";

export const getAuthState = query(async () => {
const session = await authClient.getSession();

if (isNil(session) || isNil(session.data)) {
throw redirect("/login");
}

return {
user: session.data.user,
session: session.data.session,
error: session.error,
};
}, "authState");
peerreynders
peerreynders•2mo ago
So the error isn't related to the navigate() ? Something else that happens during the logout process? Is the login route running into trouble after logout?
MaveriX89
MaveriX89OP•2mo ago
I'm suspecting it must be related to routing/navigating in some way since the exception is about path but I could be off the mark Yes, the app routes to login but then displays the error boundary fallback once it arrives This thing just gets weirder and weirder -- how about this for a brain-buster moment. So, I managed to get my app to successfully sign out without causing the exception and you're going to love the steps I just did... 1. Authenticate from Login and get redirected to /dashboard 2. On /dashboard manually update url path in browser URL bar to /login to have Login component redirect me back to /dashboard 3. Sign out from /dashboard and get redirected back to /login without crashing 🤯 I do not know what breadcrumbs we can gleam from the above workflow...
peerreynders
peerreynders•2mo ago
Another random aside: Route components support a preload prop https://docs.solidjs.com/solid-router/reference/preload-functions/preload Given that you should be able to mimic strello https://github.com/solidjs-community/strello/blob/9c9ae973d96cc045914e696757a1b5f31efc6fa1/src/lib/index.ts#L41-L49 https://github.com/solidjs-community/strello/blob/9c9ae973d96cc045914e696757a1b5f31efc6fa1/src/routes/login.tsx#L11 to navigate to /dashboard and eliminate another dependency to useNavigate (and running it inside an effect). Is it going to solve your current problem? No?
GitHub
strello/src/lib/index.ts at 9c9ae973d96cc045914e696757a1b5f31efc6fa...
Contribute to solidjs-community/strello development by creating an account on GitHub.
GitHub
strello/src/routes/login.tsx at 9c9ae973d96cc045914e696757a1b5f31ef...
Contribute to solidjs-community/strello development by creating an account on GitHub.
MaveriX89
MaveriX89OP•2mo ago
I think I finally found the problem!! It appears that ActiveUserSession was the problem all along... I went from this:
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
To this and everything works correctly:
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route path="/dashboard" component={Home} />
<Route path="/timesheets" component={Timesheets} />
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
I'm suspecting that the authState guard for redirecting was probably being overloaded in some way? Like I have that authState used in the RootLayout which contains that redirect we added to the underlying query and that should be sufficient in redirecting to Login across all the protected pages But I'm not sure about that whole explanation. I would like to really know what was going on there. It was really hard to parse the error and really pinpoint what the crux of the issue was
peerreynders
peerreynders•2mo ago
and that should be sufficient in redirecting to Login across all the protected pages
Your "Not Found" component would only work for authenticated users. Also if you implement logout as an action async isn't an issue and you can just throw redirect("/login") at the end. https://docs.solidjs.com/solid-router/reference/data-apis/use-action Consider making signin an action for the same reason. (Looks like you already did)
MaveriX89
MaveriX89OP•2mo ago
I think I can be fine with that. Basically if someone is not logged in and trying to manually navigate anywhere else, I want that to take them back to login. But excellent observation there. I did not realize that until you brought it up 😃 This is my first time trying to use SolidJS for an app I hope to productionize so all of our dialogue and your input like this has been incredibly helpful! Can't thank you enough for your time and patience @peerreynders :appwriteheart:

Did you find this page helpful?