'SolidJS Router useNavigate() throws: Error: Make sure your app is wrapped in a <Router />

Even though my App is wrapped in a Router tag, when I use useNavigate() or the <Navigate /> element I get the same error login.tsx:27 Error: Make sure your app is wrapped in a <Router />

Here is my index.tsx

/* @refresh reload */
import { render } from 'solid-js/web';

import './index.css';
import App from './App';
import { Router } from 'solid-app-router';

render(
    () => (
        <Router>
            <App />
        </Router>
    ),
    document.getElementById('root') as HTMLElement
)

App.tsx

const App: Component = () => {
    return (
        <>
            <Routes>
                <Route path="/" component={Landing} />
                <Route path="/dashboard" component={Dashboard} />
            </Routes>
        </>
    )
}

the landing component that is the parent of the Login component

import { useNavigate } from 'solid-app-router'
import { Component, createSignal, Show, onMount } from 'solid-js'
import { getIsValidSession } from '../services/session'
import '../styling/landing.css'
import CreateAccount from './landing/create-account'
import Login from './landing/login'

const Landing: Component = () => {
    const [displayLogin, setDisplayLogin] = createSignal(true)

    return (
        <div class="landing text-center">
            <main class="form-signin">
                <img
                    class="mb-4"
                    src="src/assets/original/tracker-image.png"
                    alt=""
                    width="72"
                    height="85"
                />
                <Show when={displayLogin()} fallback={<CreateAccount />}>
                    <Login />
                </Show>

                <Show
                    when={displayLogin()}
                    fallback={
                        <button
                            class="w-100 btn btn-lg btn-primary mt-3"
                            onClick={toggleDisplay}>
                            Login Existing User
                        </button>
                    }>
                    <button
                        class="w-100 btn btn-lg btn-primary mt-3"
                        onClick={toggleDisplay}>
                        Create Account
                    </button>
                </Show>
            </main>
        </div>
    )
}

export default Landing

and the Login component

import { useNavigate } from 'solid-app-router'
import { Component, createSignal } from 'solid-js'
import Response from '../../models/response'
import { PostSession, Session } from '../../models/session'
import * as session from '../../services/session'

const Login: Component = () => {
    const [email, setEmail] = createSignal('')
    const [password, setPassword] = createSignal('')

    const login = async (event: any) => {
        event.preventDefault()
        try {
            console.log(`Login: ${email()}, Password: ${password()}`)
            const res = await session.login({
                email: email(),
                password: password(),
            } as PostSession)

            if (res.status !== 200) {
                window.alert('Login failed')
            } else {
                const navigate = useNavigate()
                navigate('/dashboard', { replace: true })
            }
        } catch (error) {
            console.error(error)
        }
    }

    return (
        <form>
            <h1 class="h3 mb-3 fw-normal">Please sign in</h1>

            <div class="form-floating">
                <input
                    type="email"
                    class="form-control"
                    id="floatingInput"
                    placeholder="[email protected]"
                    value={email()}
                    onChange={(e: any) => setEmail(e.target.value)}
                />
                <label for="floatingInput">Email address</label>
            </div>
            <div class="form-floating">
                <input
                    type="password"
                    class="form-control"
                    id="floatingPassword"
                    placeholder="Password"
                    value={password()}
                    onChange={(e: any) => setPassword(e.target.value)}
                />
                <label for="floatingPassword">Password</label>
            </div>

            <div class="checkbox mb-3">
                <label>
                    <input type="checkbox" value="remember-me" /> Remember me
                </label>
            </div>
            <button
                class="w-100 btn btn-lg btn-primary"
                onClick={e => login(e)}>
                Sign in
            </button>
        </form>
    )
}

export default Login

What is the correct way to setup the Router for my use case?



Solution 1:[1]

The problem here (I think) is that you are using useNavigate() in a event handler. The function needs to be called synchronously during render "within the tree", rather than in a callback, which doesn't have the same execution context. Try this:

    const [email, setEmail] = createSignal('')
    const [password, setPassword] = createSignal('')
    const navigate = useNavigate()

and then use it in your event handler as you were:

        } else {
            navigate('/dashboard', { replace: true })
        }

Does that work?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 FoolsWisdom