Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/preserve-layouts-between-child-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/router": patch
---

fix #588: preserve nested route layouts between sibling navigations
14 changes: 6 additions & 8 deletions src/routers/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type {Component} from "solid-js";
import type {JSX} from "@solidjs/web";
import {createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack} from "solid-js";
import {createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack, Show} from "solid-js";
import {getRequestEvent, isServer, type RequestEvent} from "@solidjs/web";
import {
createRouteContext,
Expand Down Expand Up @@ -149,13 +149,11 @@ export function Routes(props: { routerState: RouterContext; branches: () => Bran
}

const createOutlet = (child: () => RouteContext | undefined) => {
return () => {
const c = child();
if (c) {
return <RouteContextObj value={c}>{c.outlet()}</RouteContextObj>;
}
return undefined;
};
return () => (
<Show when={child()} keyed>
{c => <RouteContextObj value={c}>{c.outlet()}</RouteContextObj>}
</Show>
);
};

// for data only mode with single flight mutations
Expand Down
64 changes: 63 additions & 1 deletion test/client-navigation.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { render } from "@solidjs/web";
import { createEffect, createMemo, Loading } from "solid-js";
import { vi } from "vitest";
import { createRouter, memoryHistory, useNavigate, useParams, type Navigator } from "../src/index.js";
import {
createRouter,
memoryHistory,
useNavigate,
useParams,
type Navigator
} from "../src/index.js";

const settle = async (ms = 0) => {
await new Promise<void>(resolve => queueMicrotask(() => resolve()));
Expand Down Expand Up @@ -236,6 +242,62 @@ describe("Client navigation should", () => {
}
});

test("preserve parent layouts when navigating between sibling child routes (#588)", async () => {
const div = document.createElement("div");
document.body.appendChild(div);

let mounts = 0;
let navigate!: Navigator;

const Layout = (props: { children?: any }) => {
mounts++;
navigate = useNavigate();
return (
<div data-route="layout">
layout
{props.children}
</div>
);
};

const Router = createRouter({
routes: [
{
path: "/",
component: Layout,
children: [
{ path: "/a", component: () => <div data-route="a">A</div> },
{ path: "/b", component: () => <div data-route="b">B</div> }
]
}
] as const,
history: memoryHistory("/a")
});

const dispose = render(() => <Router />, div);

try {
await settle();
expect(mounts).toBe(1);
expect(div.querySelector('[data-route="a"]')?.textContent).toBe("A");

navigate("/b");
await settle();
expect(mounts).toBe(1);
expect(div.querySelector('[data-route="b"]')?.textContent).toBe("B");
expect(div.querySelector('[data-route="a"]')).toBeNull();

navigate("/a");
await settle();
expect(mounts).toBe(1);
expect(div.querySelector('[data-route="a"]')?.textContent).toBe("A");
expect(div.querySelector('[data-route="b"]')).toBeNull();
} finally {
dispose();
div.remove();
}
});

test("call preload with the route's own params during navigation", async () => {
const div = document.createElement("div");
document.body.appendChild(div);
Expand Down