diff --git a/e2e/solid-start/basic/tests/special-characters.spec.ts b/e2e/solid-start/basic/tests/special-characters.spec.ts index 9549d491ce1..6bbb240a842 100644 --- a/e2e/solid-start/basic/tests/special-characters.spec.ts +++ b/e2e/solid-start/basic/tests/special-characters.spec.ts @@ -122,10 +122,9 @@ test.describe('Unicode route rendering', () => { await expect(page.getByTestId('special-hash-heading')).toBeInViewport() - // TODO: this should work but seems to be a bug in reactivity on Solid Dynamic component. Still investigating. - // await expect(page.getByTestId('special-hash-link-1')).toContainClass( - // 'font-bold', - // ) + await expect(page.getByTestId('special-hash-link-1')).toContainClass( + 'font-bold', + ) await expect(page.getByTestId('special-hash-link-2')).not.toContainClass( 'font-bold', diff --git a/packages/solid-router/src/link.tsx b/packages/solid-router/src/link.tsx index 4c70bc15e76..e3c312477a0 100644 --- a/packages/solid-router/src/link.tsx +++ b/packages/solid-router/src/link.tsx @@ -10,12 +10,15 @@ import { preloadWarning, removeTrailingSlash, } from '@tanstack/router-core' + +import { isServer } from '@tanstack/router-core/isServer' import { Dynamic } from 'solid-js/web' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' import { useIntersectionObserver } from './utils' +import { useHydrated } from './ClientOnly' import type { AnyRouter, Constrain, @@ -40,6 +43,9 @@ export function useLinkProps< ): Solid.ComponentProps<'a'> { const router = useRouter() const [isTransitioning, setIsTransitioning] = Solid.createSignal(false) + const shouldHydrateHash = !isServer && !!router.options.ssr + const hasHydrated = useHydrated() + let hasRenderFetched = false const [local, rest] = Solid.splitProps( @@ -115,6 +121,10 @@ export function useLinkProps< 'unsafeRelative', ]) + const currentLocation = useRouterState({ + select: (s) => s.location, + }) + const currentSearch = useRouterState({ select: (s) => s.location.searchStr, }) @@ -194,51 +204,51 @@ export function useLinkProps< const preloadDelay = () => local.preloadDelay ?? router.options.defaultPreloadDelay ?? 0 - const isActive = useRouterState({ - select: (s) => { - if (externalLink()) return false - if (local.activeOptions?.exact) { - const testExact = exactPathTest( - s.location.pathname, - next().pathname, - router.basepath, - ) - if (!testExact) { - return false - } - } else { - const currentPathSplit = removeTrailingSlash( - s.location.pathname, - router.basepath, - ).split('/') - const nextPathSplit = removeTrailingSlash( - next()?.pathname, - router.basepath, - )?.split('/') - - const pathIsFuzzyEqual = nextPathSplit?.every( - (d, i) => d === currentPathSplit[i], - ) - if (!pathIsFuzzyEqual) { - return false - } + const isActive = Solid.createMemo(() => { + if (externalLink()) return false + if (local.activeOptions?.exact) { + const testExact = exactPathTest( + currentLocation().pathname, + next().pathname, + router.basepath, + ) + if (!testExact) { + return false } - - if (local.activeOptions?.includeSearch ?? true) { - const searchTest = deepEqual(s.location.search, next().search, { - partial: !local.activeOptions?.exact, - ignoreUndefined: !local.activeOptions?.explicitUndefined, - }) - if (!searchTest) { - return false - } + } else { + const currentPathSplit = removeTrailingSlash( + currentLocation().pathname, + router.basepath, + ).split('/') + const nextPathSplit = removeTrailingSlash( + next()?.pathname, + router.basepath, + )?.split('/') + + const pathIsFuzzyEqual = nextPathSplit?.every( + (d, i) => d === currentPathSplit[i], + ) + if (!pathIsFuzzyEqual) { + return false } + } - if (local.activeOptions?.includeHash) { - return s.location.hash === next().hash + if (local.activeOptions?.includeSearch ?? true) { + const searchTest = deepEqual(currentLocation().search, next().search, { + partial: !local.activeOptions?.exact, + ignoreUndefined: !local.activeOptions?.explicitUndefined, + }) + if (!searchTest) { + return false } - return true - }, + } + + if (local.activeOptions?.includeHash) { + const currentHash = + shouldHydrateHash && !hasHydrated() ? '' : currentLocation().hash + return currentHash === next().hash + } + return true }) const doPreload = () =>