S
SolidJS17mo ago
Martnart

SSR Hydration error in ChildRoute with Context set in ParentRoute and intermediate Context Update

I have a ParentRoute that retrieves RouteData and uses that data to populate a Context
export default function Main() {
const data = useRouteData();

return (
<MyContext someData={data()?.anArray}>
<Outlet />
</MyContext>
)
}
export default function Main() {
const data = useRouteData();

return (
<MyContext someData={data()?.anArray}>
<Outlet />
</MyContext>
)
}
In my child route, I get hydration errors when accessed directly via SSR. With Client-side navigation it works fine.
export default function Child() {
const [someItem] = useMyContext()

return (
<Show when={someItem()} fallback={<Placeholder />} keyed>
{(item) => <p>{item}</p>}
</Show>
)
}
export default function Child() {
const [someItem] = useMyContext()

return (
<Show when={someItem()} fallback={<Placeholder />} keyed>
{(item) => <p>{item}</p>}
</Show>
)
}
Context looks like this
const TheContext = createContext([
() => undefined,
() => [],
])

export const MyContext(props: { anArray?: string[], children: JSXElement }) {
const [theArray, setTheArray] = createSignal(props.anArray || [])

const someItem = createMemo(() => theArray[0])

const theContext = [
someItem,
theArray,
]

return (
<TheContext.Provider value={theContext }>
{props.children}
</TheContext.Provider>
)
}

export const useMyContext = () => useContext(MyContext)
const TheContext = createContext([
() => undefined,
() => [],
])

export const MyContext(props: { anArray?: string[], children: JSXElement }) {
const [theArray, setTheArray] = createSignal(props.anArray || [])

const someItem = createMemo(() => theArray[0])

const theContext = [
someItem,
theArray,
]

return (
<TheContext.Provider value={theContext }>
{props.children}
</TheContext.Provider>
)
}

export const useMyContext = () => useContext(MyContext)
Any ideas what went wrong here? ❤️
1 Reply
Martnart
Martnart17mo ago
There's a bit more to it, that's I think relevant. In some component the context is manipulated as well.
export const BreadCrumbs = () => {
const [,, selectedId, { select }] = useMyContext()
const [query] = useSearchParams()

createEffect(() => {
if (query.id && !selectedId()) select(query.id)
})

return <SomeStuff />
}
export const BreadCrumbs = () => {
const [,, selectedId, { select }] = useMyContext()
const [query] = useSearchParams()

createEffect(() => {
if (query.id && !selectedId()) select(query.id)
})

return <SomeStuff />
}
And then the updated, more complete, Context looks like this:
const TheContext = createContext([
() => undefined,
() => [],
() => '',
{ select: () => undefined }
])

export const MyContext(props: { anArray?: any[], children: JSXElement }) {
const [theArray, setTheArray] = createSignal(props.anArray || [])
const [anId, setTheId] = createSignal('')

const someItem = createMemo(() => theArray().find((arrayItem) => arrayItem.id === anId()))

const theContext = [
someItem,
theArray,
anId,
{ select: setTheId }
]

return (
<TheContext.Provider value={theContext }>
{props.children}
</TheContext.Provider>
)
}

export const useMyContext = () => useContext(MyContext)
const TheContext = createContext([
() => undefined,
() => [],
() => '',
{ select: () => undefined }
])

export const MyContext(props: { anArray?: any[], children: JSXElement }) {
const [theArray, setTheArray] = createSignal(props.anArray || [])
const [anId, setTheId] = createSignal('')

const someItem = createMemo(() => theArray().find((arrayItem) => arrayItem.id === anId()))

const theContext = [
someItem,
theArray,
anId,
{ select: setTheId }
]

return (
<TheContext.Provider value={theContext }>
{props.children}
</TheContext.Provider>
)
}

export const useMyContext = () => useContext(MyContext)
Now it's getting a bit convoluted. :/ Any help would be much appreciated, though. I'd like to add that when logging the important bits of this chain the Select action from the context runs earlier than the log for Creating Child Route so by that time, the Context should already have been updated. At least for client-side rendering, that is. It is fixed if I cut out the intermediate Context update step, remove if (query.id && !selectedId()) select(query.id) and instead pass query.id as initial Value to the context from Parent Route. No more hydration errors. This solves my use-case so it's good enough for me, but is this not working otherwise expected?