S
SolidJS•3w ago
flippyflops

Reactivity in store

I think I may be missing the point of stores or just how to use them because I simply can't get it to trigger reactivity in a component. Below is a barebones example where I'm trying to have a value in a store and a function to update it - when the value changes I want to render it on the screen in a paragraph tag. Is there some obvious thing I'm missing here?
import { createContext, ParentProps, useContext } from "solid-js";
import { createStore } from "solid-js/store";

const FoobarContext = createContext<{
name: string;
changeName: () => void;
}>();

export const FoobarProvider = (props: ParentProps<unknown>) => {
const [store, setStore] = createStore({
name: "Jeff",
});

return (
<FoobarContext.Provider
value={{
...store,
changeName: () => {
setStore("name", "John");
},
}}
>
{props.children}
</FoobarContext.Provider>
);
};

const FoobarComponent = () => {
const { name, changeName } = useContext(FoobarContext)!;

return (
<>
<p>{name}</p>
<button onClick={changeName}>Change Name</button>
</>
);
};

export const Foobar = () => (
<FoobarProvider>
<FoobarComponent />
</FoobarProvider>
);
import { createContext, ParentProps, useContext } from "solid-js";
import { createStore } from "solid-js/store";

const FoobarContext = createContext<{
name: string;
changeName: () => void;
}>();

export const FoobarProvider = (props: ParentProps<unknown>) => {
const [store, setStore] = createStore({
name: "Jeff",
});

return (
<FoobarContext.Provider
value={{
...store,
changeName: () => {
setStore("name", "John");
},
}}
>
{props.children}
</FoobarContext.Provider>
);
};

const FoobarComponent = () => {
const { name, changeName } = useContext(FoobarContext)!;

return (
<>
<p>{name}</p>
<button onClick={changeName}>Change Name</button>
</>
);
};

export const Foobar = () => (
<FoobarProvider>
<FoobarComponent />
</FoobarProvider>
);
11 Replies
flippyflops
flippyflops•3w ago
I think it may be worth noting that using createSignal and passing whatever value that is to the context triggers a re-render as expected.
REEEEE
REEEEE•3w ago
If you spread the store like that to the context, you've effectively read the values and thus disabled reactivity because the underlying functions of the store that were managing the reactivity have been called as for why the signal works, if you're simply passing the signal along via value={{signalValue, changeName(){}}} and reading it as
const { signalValue, changeName } = useContext(FoobarContext)

...
<p> {signalValue().name} </p>
const { signalValue, changeName } = useContext(FoobarContext)

...
<p> {signalValue().name} </p>
then that will work just fine because the signal is called in a place where reactivity is preserved, the jsx. If you did this though, value={{...signalValue(), changeName(){}}}, this wouldn't work because you're assigning the values to the value prop and the value prop isn't reactive The solution I would go with is to pass the store to the context instead if you need all the values. Otherwise you can use object getters but you won't be able to destructure the context like you've done. Or make functions to get specific properties from the store
flippyflops
flippyflops•3w ago
I thought the same about spreading the store when I was running through some testing, but for some reason this also doesn't work (if this example is what you mean):
<FoobarContext.Provider
value={{
name: store.name,
changeName: () => {
setStore("name", "john");
},
}}
>
{props.children}
</FoobarContext.Provider>
<FoobarContext.Provider
value={{
name: store.name,
changeName: () => {
setStore("name", "john");
},
}}
>
{props.children}
</FoobarContext.Provider>
REEEEE
REEEEE•3w ago
again, you've read the value and nothing will be tracked Each value in a store is a signal underneath, once you read a property the signal is called and you have the value
flippyflops
flippyflops•3w ago
ah i see so it simply needs to be
{{store, changeName}}
{{store, changeName}}
?
REEEEE
REEEEE•3w ago
Yup
foolswisdom
foolswisdom•3w ago
And specifically, tracking the signal of a given property occurs at the location of property access
REEEEE
REEEEE•3w ago
<FoobarContext.Provider
value={{
get name(){
return store.name
},
changeName: () => {
setStore("name", "john");
},
}}
>
{props.children}
</FoobarContext.Provider>
<FoobarContext.Provider
value={{
get name(){
return store.name
},
changeName: () => {
setStore("name", "john");
},
}}
>
{props.children}
</FoobarContext.Provider>
This will work because the property is read inside a function (the object getter) but when you do
const { name, changeName } = useContext(FoobarContext)!;
const { name, changeName } = useContext(FoobarContext)!;
It will call the name getter and only give you the value it was at that moment and won't react to changes to store.name But if you did this instead
const context = useContext(FoobarContext)!;

...

<p>{context.name}</p>
const context = useContext(FoobarContext)!;

...

<p>{context.name}</p>
It will be reactive
flippyflops
flippyflops•3w ago
Okay I see, that makes sense. Is this specifically pointed out in the docs? I read through but I suppose perhaps I missed it. If it isn't I may make a PR because this has been tripping me up for a while and ty for the explanation, this was super helpful! 🙂
REEEEE
REEEEE•3w ago
No problem It's mentioned here a bit on the new docs for general reactivity: https://docs.solidjs.com/concepts/intro-to-reactivity But the tutorial on the main site might be more beneficial Maybe worth a mention to the docs team
flippyflops
flippyflops•3w ago
Oh yeah, that worked like a charm and really simplified my code too. My date picker's keyboard navigation is working great now -- thanks again!