#Initializing a Context using createServerData$ isn't working.

18 messages · Page 1 of 1 (latest)

lyric walrus
#

I'm trying to initialize a Context with data loaded from a DB, but the setter function isn't working.

If the context is initialized from createServerData$ (look for [2] below), the getter returns the value from the store, but the setter is only ever called with undefined (look for [1]).

If the context is initialized from a constant [1, 2, 3], then both the getter and the setter work as expected.

My basic context provider looks like this:

export function NumsProvider(props: {init: number[], children?: JSXElement}) {
    const [getter, setter] = createStore({nums: props.init})
    const context = {
        getter,
        setter,
        incr() {
            setter("nums", all => {
                console.log(`nums is ${JSON.stringify(all)}`) // [1] why is `all` undefined here?
                return all?.map(one => one + 1)
            })
        },
    }
    return (
        <NumsContext.Provider value={context}>
            {props.children}
        </NumsContext.Provider>
    )
}

It's used in a basic parent component that wraps a child. The child uses the context.

export default function Parent() {
    const nums = createServerData$(getNums) // getNums() gets Promise<number[]> from a DB
    return (
        <main>
            <NumsProvider init={nums()}> // [2] if instead of nums() we just use [1, 2, 3], it works
                <Child />
            </NumsProvider>
        </main>
    )
}

The child just gets the values from the context and has a button to trigger the setter.

export default function Child() {
    const { getter, incr } = useFakeStore()!
    return (
        <div>
            <button type="submit" onClick={incr}>INCR</button>
            <div>
                <For each={getter.nums}>
                    {(item, index) => <div data-index={index()}>{JSON.stringify(item)}</div>}
                </For>
            </div>
        </div>
    )
}
tender kettle
#

Hi! I'm fairly new to Solid, so I'm not sure if I am on point here, but I would assume that when you create the NumProvider the server call has not yet resolved, hence you pass undefined as an initial Value to createStore. You could listen to the changes of the data returned from the server with something like createEffect(() => props.init && setter(props.init)) in your context, I suppose

lyric walrus
#

I think that's very likely, but then I'm confused why the store doesn't update when the server call does resolve. I wonder what the "happy path" way of initializing a context looks like.

#

That said, adding your suggested fix after creating the store does fix the behavior I saw! And one other render issue I was struggling with!

tender kettle
#

createStore is a one-off operation. It proxifies whatever you pass into it. While I'm also curious to see a "happy path", it does make sense that it would behave that way. One option that I'm using with an AuthContextProvider is to create the context only after making sure that the value exists. In my case it queries the user and then has the Contexts in a conditional show. That way I know it is the expected data when the Context is built

return (
  <Show when={data()} fallback={"Loading..."} keyed>
    {(user) => (
      <MyContext initialValue={user.hobbies}>
        <Outlet />
      </MyContext>
    )}
  </Show>
)    
#

But it's situational of course

lyric walrus
#

Interesting workaround!

#

I guess by this point the app is no longer actually rendered on the server?

tender kettle
#

Quite honestly, I don't know. I think if you have that readily available in routeData it would work. I had some SSR hydration issues but currently it's all working fine and hydration markers are attached to elements deep into my nested routes, so I have to assume SSR is working as expected.

lyric walrus
#

At any rate, you unblocked me at least in the immediate term, and I really appreciate that!

#

And it does actually seem to happen serverside, at least if the HTML sent from the server is any evidence!

tender kettle
lyric walrus
#

👍

lyric walrus
#

Uhh, one last question: how did you end up with the lambda-ish version of Show? I'm not sure on when Show reruns the children when it looks like that, and the docs aren't too clear...

tender kettle
#

Afaik, if you provide keyed it will pass whatever the Show condition was as an argument into the child function. You could omit keyed and just have regular expressions like

<Show when={something()}>
  <SomeComponent myThing={something()} /> // TS error something may be undefined
</Show>

But TS is not clever enough here to know that if it's inside a Show component, it is defined by definition.
That's at least how I understand it

#

Tbf, keyed probably does other things underneath the hood, I haven't actually checked the implementation details, but that is the one obvious benefit that I usually use it for.

lyric walrus
#

Yeah, the non-lambda Show is the one I usually use (for clientside things), the docs suggest that there's some important difference between the plain and lambda versions.

keen schooner
#

keyed will rerender everything under the Show whenever when changes