From 0484065b8e07d28b5e04fed350cb7c9329ff2e11 Mon Sep 17 00:00:00 2001 From: Jungzl <13jungzl@gmail.com> Date: Mon, 24 Aug 2026 00:44:23 +0800 Subject: [PATCH] fix #588: preserve nested route layouts between sibling navigations --- .../preserve-layouts-between-child-routes.md | 5 ++ src/routers/components.tsx | 14 ++-- test/client-navigation.spec.tsx | 64 ++++++++++++++++++- 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 .changeset/preserve-layouts-between-child-routes.md diff --git a/.changeset/preserve-layouts-between-child-routes.md b/.changeset/preserve-layouts-between-child-routes.md new file mode 100644 index 00000000..367f7154 --- /dev/null +++ b/.changeset/preserve-layouts-between-child-routes.md @@ -0,0 +1,5 @@ +--- +"@solidjs/router": patch +--- + +fix #588: preserve nested route layouts between sibling navigations diff --git a/src/routers/components.tsx b/src/routers/components.tsx index 35b01d96..719b07f3 100644 --- a/src/routers/components.tsx +++ b/src/routers/components.tsx @@ -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, @@ -149,13 +149,11 @@ export function Routes(props: { routerState: RouterContext; branches: () => Bran } const createOutlet = (child: () => RouteContext | undefined) => { - return () => { - const c = child(); - if (c) { - return {c.outlet()}; - } - return undefined; - }; + return () => ( + + {c => {c.outlet()}} + + ); }; // for data only mode with single flight mutations diff --git a/test/client-navigation.spec.tsx b/test/client-navigation.spec.tsx index 6d230705..9b4948a2 100644 --- a/test/client-navigation.spec.tsx +++ b/test/client-navigation.spec.tsx @@ -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(resolve => queueMicrotask(() => resolve())); @@ -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 ( +
+ layout + {props.children} +
+ ); + }; + + const Router = createRouter({ + routes: [ + { + path: "/", + component: Layout, + children: [ + { path: "/a", component: () =>
A
}, + { path: "/b", component: () =>
B
} + ] + } + ] as const, + history: memoryHistory("/a") + }); + + const dispose = render(() => , 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);