S
SolidJS•7mo ago
MikeM42

Solid Router within Router

Hi I am encountering something weird and can't figure out either what I am doing wrong. I have an initial Router a the App.tsx level like so: <Router base="/"> <Route path="/" component={StartApp} /> <Route path="/contract" component={ContractLifecycle}/> <Route path="*404" component={()=> <div>Page Not found {useLocation().pathname}!</div>} /> </Router> Then under ContractLifecycle I have another router like so: <Router base="contract/"> <Route path="/" component={LoadEncode}/> <Route path="/prepare" component={Prepare}/> </Router> When I load the App, the StartApp component loads fine, but when I navigate to "contract/", the LoadEncode component doesn't load, which is what I would have expected (same as / loading for the App router). If however I reload the page (click reload in the browser) then the LoadEncode component page shows up. I also tried without the "/" at the end like so: <Router base="contract"> <Route path="" component={LoadEncode}/> <Route path="/prepare" component={Prepare}/> </Router> But the result is the same.
16 Replies
peerreynders
peerreynders•7mo ago
I'm aware of Nested Routes. I am not aware that nested routers is a thing.
GitHub
GitHub - solidjs/solid-router: A universal router for Solid inspire...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
MikeM42
MikeM42OP•7mo ago
Hmm I wonder how I make this work, the problem is not so much the nested routes, I could declre the routes without the Router wrapper but I want those nested routes to be within a Context, and having Route wrapped in a context doesn't seem to work that well, it works for Router but not Route. If I wrap the routes in a Context Provider what I get on the screen is: [object Object][object Object] Because its trying to render the routes which obviously dooesn't work that well. Maybe I need to put some other logic in my Contexct provider and rather than just doing <ContractContext.Provider value={value}> {props.children} </ContractContext.Provider> I should maybe handle the props.children that are routes differently, but I don't kno whow 😩
peerreynders
peerreynders•7mo ago
but I want those nested routes to be within a Context
Why is it important to restrict access to that context to specific routes?
MikeM42
MikeM42OP•7mo ago
Its more a segregation of concerns thing rather than anything else, I could put the context at the top level but then it feels like I am shoving everything at the top of the tree. Which could end up being very hard to maintain in the long run. The idea here was to have lets say a "sub-functionality" in the web-app that had its own context and its own routes. Makes things cleaner and easier to maintain as independent entities. I guess I could always create a master context, and then "attach" other contexts to that through a map or something, and the always useContext on the master context, and pull the sub-context from that, possibly even ad an remove the sub-context through onMount and onCleanup or something but would have been nicer if I could do it in the SolidJS framework.
peerreynders
peerreynders•7mo ago
SolidStart supports what you are looking for via layouts for nested routes. Now I'm going out on a limb here: If you write the provider to only initialize once regardless how many times you call it you could just put the provider at the root of the components that need it. I'm still trying to figure out how to emulate nested route layouts with a vanilla router ... Once you've got the provider sorted out (initializes only once) you can pull the provider into a layout: Rough sketch:
import { render } from 'solid-js/web';
import { Router, Route } from '@solidjs/router';

//...

const App = (props) => (
<>
<h1>My Site with lots of pages</h1>
{props.children}
</>
);

render(
() => (
<Router root={App}>
<Route path="/contract">
<Route
path="/"
component={() => (
<ContractLayout>
<LoadEncode />
</ContractLayout>
)}
/>
<Route
path="/prepare"
component={() => (
<ContractLayout>
<Prepare />
</ContractLayout>
)}
/>
</Route>
<Route path="/" component={StartApp} />
<Route
path="*404"
component={() => <div>Page Not found {useLocation().pathname}!</div>}
/>
</Router>
),
document.getElementById('app')
);
import { render } from 'solid-js/web';
import { Router, Route } from '@solidjs/router';

//...

const App = (props) => (
<>
<h1>My Site with lots of pages</h1>
{props.children}
</>
);

render(
() => (
<Router root={App}>
<Route path="/contract">
<Route
path="/"
component={() => (
<ContractLayout>
<LoadEncode />
</ContractLayout>
)}
/>
<Route
path="/prepare"
component={() => (
<ContractLayout>
<Prepare />
</ContractLayout>
)}
/>
</Route>
<Route path="/" component={StartApp} />
<Route
path="*404"
component={() => <div>Page Not found {useLocation().pathname}!</div>}
/>
</Router>
),
document.getElementById('app')
);
MikeM42
MikeM42OP•7mo ago
That could indeed be an option, I'll see if I can get the provider to only initialize once. Not as elegant as I would have wanted but it could indeed work.
peerreynders
peerreynders•7mo ago
Frequently elegant isn't equivalent to resilient.
MikeM42
MikeM42OP•7mo ago
I'd beg to differ, but I think that might lead us down a long winded mostly philosophical debate. That being said I was able to do what you proposed. Had to externalize some properties etc. but it works, so thats cool. Thanks for the assist!
peerreynders
peerreynders•7mo ago
make it work, make it right, make it fast Here you go - you can specify a layout component at the wrapper level. I just wasn't sure. https://stackblitz.com/edit/stackblitz-starters-p8qprb?file=src%2Fapp.tsx
function App() {
return (
<Router
root={(props) => {
return (
<MetaProvider>
<Title>Solid - Basic</Title>
<a href="/">home</a>
<a href="/contract">contract</a>
<a href="/contract/prepare">prepare</a>
<Suspense>{props.children}</Suspense>
</MetaProvider>
);
}}
>
<Route path="/contract" component={ContractLayout}>
<Route path="/" component={Contract} />
<Route path="/prepare" component={Prepare} />
</Route>
<Route path="/" component={Home} />
<Route path="*404" component={NotFound} />
</Router>
);
}
function App() {
return (
<Router
root={(props) => {
return (
<MetaProvider>
<Title>Solid - Basic</Title>
<a href="/">home</a>
<a href="/contract">contract</a>
<a href="/contract/prepare">prepare</a>
<Suspense>{props.children}</Suspense>
</MetaProvider>
);
}}
>
<Route path="/contract" component={ContractLayout}>
<Route path="/" component={Contract} />
<Route path="/prepare" component={Prepare} />
</Route>
<Route path="/" component={Home} />
<Route path="*404" component={NotFound} />
</Router>
);
}
peerreynders
StackBlitz
solidjs/router Layout for nested routes - StackBlitz
A Solid TypeScript project based on @solidjs/meta, @solidjs/router, solid-js, typescript, vite and vite-plugin-solid
Katja (katywings)
Katja (katywings)•7mo ago
@MikeM42 I also stumbled on the issue regarding nested routers and figured out a (experimental) workaround, let me know if you wanna see it 😅
MikeM42
MikeM42OP•7mo ago
Always interested yes, would be great to see if there's a way to solve this.
Katja (katywings)
Katja (katywings)•7mo ago
Here ya go 🙂
import { Router, useLocation, useNavigate, Location } from "@solidjs/router";
import { ParentComponent, createEffect } from "solid-js";

export const NestedRouter: ParentComponent = (props) => {
const location = useLocation();
const navigate = useNavigate();
const path = (location: Location) => location.pathname + location.search + location.hash;

const Root: ParentComponent = (props) => {
const innerLocation = useLocation();
const innerNavigate = useNavigate();

type Locations = { in: string; out: string };

let prev: Locations | undefined = undefined;
createEffect(function () {
const locations = {
in: path(innerLocation),
out: path(location),
};

if (prev && locations.in !== locations.out) {
if (prev.in === locations.in) {
console.log("inner nav", locations.out);
innerNavigate(locations.out, { replace: true, state: location.state });
}
if (prev.out === locations.out) {
console.log("outer nav", locations.in);
navigate(locations.in, { replace: true, state: innerLocation.state });
}
}

prev = locations;
});

return props.children;
};

return (
<Router root={Root}>
{props.children}
</Router>
);
};
import { Router, useLocation, useNavigate, Location } from "@solidjs/router";
import { ParentComponent, createEffect } from "solid-js";

export const NestedRouter: ParentComponent = (props) => {
const location = useLocation();
const navigate = useNavigate();
const path = (location: Location) => location.pathname + location.search + location.hash;

const Root: ParentComponent = (props) => {
const innerLocation = useLocation();
const innerNavigate = useNavigate();

type Locations = { in: string; out: string };

let prev: Locations | undefined = undefined;
createEffect(function () {
const locations = {
in: path(innerLocation),
out: path(location),
};

if (prev && locations.in !== locations.out) {
if (prev.in === locations.in) {
console.log("inner nav", locations.out);
innerNavigate(locations.out, { replace: true, state: location.state });
}
if (prev.out === locations.out) {
console.log("outer nav", locations.in);
navigate(locations.in, { replace: true, state: innerLocation.state });
}
}

prev = locations;
});

return props.children;
};

return (
<Router root={Root}>
{props.children}
</Router>
);
};
Its very experimental. It acts like a layer that proxies outer-router navigation events to the NestedRouter and vice-versa 😅 You would use it e.g. like so:
// in src/routes/admin/index.tsx
<NestedRouter>
<Route path="/admin">
<Route path="/users" />
<Route path="/settings" />
...more admin routes...
</Route>
</NestedRouter>
// in src/routes/admin/index.tsx
<NestedRouter>
<Route path="/admin">
<Route path="/users" />
<Route path="/settings" />
...more admin routes...
</Route>
</NestedRouter>
MikeM42
MikeM42OP•6mo ago
That does look pretty cool! I think I may have a use for this, will let you know when I've had a chance to try it out. How do you declare the Route to the component under which you declare the nested route though? You must declare it in some way that it will render any component unde /xyz/something through the component that holds the nested route, no?
Katja (katywings)
Katja (katywings)•6mo ago
In my case the parent router is SolidStart's fs router, so I literally create a fs based route and put my NestedRouter inside of it 😅 , eg.
// in src/routes/admin/[...route].tsx
const Admin = function() {
return <NestedRouter>
<Route path="/admin">
<Route path="/users" />
<Route path="/settings" />
...more admin routes...
</Route>
</NestedRouter>
}

export default Admin;
// in src/routes/admin/[...route].tsx
const Admin = function() {
return <NestedRouter>
<Route path="/admin">
<Route path="/users" />
<Route path="/settings" />
...more admin routes...
</Route>
</NestedRouter>
}

export default Admin;
MikeM42
MikeM42OP•6mo ago
I think I see, it didn't quite work for what I had in mind so will need to experiment a bit more on how to integrate it in my flow. Many thanks for your help though.
Katja (katywings)
Katja (katywings)•6mo ago
I see 😅, I hope you find a solution that works for your use case!
Want results from more Discord servers?
Add your server