7

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="name@example.com"
                    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?

Ray Kochenderfer
  • 333
  • 1
  • 6
  • 15

1 Answers1

9

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?

FoolsWisdom
  • 821
  • 4
  • 6
  • 1
    Yes, it does! Good catch! – Ray Kochenderfer May 04 '22 at 12:30
  • This isn't obvious. It should work anywhere. And it would make sense for it to work anywhere because there's only one address bar per app, thus the API can be global and whatever Router needs to listen to it should be able to regardless of the tree. – trusktr Apr 29 '23 at 23:42
  • @trusktr I'm pretty sure the router was designed with the possibility of multiple routers on the same page in mind. All of the router primitives work this way, not just useNavigate. – FoolsWisdom Apr 30 '23 at 12:27