Reputation: 43
In React all props are updated and propagated to children automatically which is nice but it slows down and requires lots of optimization at some point.
So I'm building an app with SolidJS using Context
+ createStore
patterng and I'm having problems with consuming that state.
I'd like to create AppProvider
component that manages State
props and Dispatch
functions. The Provider will be performing all operations on appStore
, implement functions and serve them all via AppContextState
and AppContextDispatch
providers.
Then I need to consume that data to update components that are dependent on it reactively.
Look at the code below:
/// index.tsx
import { render } from 'solid-js/web';
import { AppProvider } from '@/providers/AppProvider';
import App from './App';
render(() => (
<AppProvider>
<App />
</AppProvider>
), document.getElementById('root') as HTMLElement);
/// AppProvider.tsx
import { createContext, useContext, JSX } from 'solid-js';
import { createStore } from 'solid-js/store';
// Interfaces
interface IAppState {
isConnected: boolean;
user: { name: string; }
}
interface IAppDispatch {
connect: () => Promise<void>;
disconnect: () => Promise<void>;
}
// Initialize
const initialState = {
isConnected: false,
user: { name: '' }
}
const initialDispatch = {
connect: () => {},
disconnect: () => {}
}
// Contexts
const AppContextState = createContext<IAppState>();
const AppContextDispatch = createContext<IAppDispatch>();
export const useAppState = () => useContext(AppContextState);
export const useAppDispatch = () => useContext(AppContextDispatch);
// Provider
export const AppProvider = (props: { children: JSX.Element }) => {
const [appStore, setAppStore] = createStore<IAppState>(initialState);
async function connect() {
setAppStore("isConnected", true);
setAppStore("user", "name", 'Chad');
}
async function disconnect() {
setAppStore("isConnected", false);
setAppStore("user", "name", '');
}
return (
<AppContextState.Provider value={appStore}>
<AppContextDispatch.Provider value={{ connect, disconnect }}>
{props.children}
</AppContextDispatch.Provider>
</AppContextState.Provider>
)
}
/// App.tsx
import { useAppState, useAppDispatch } from '@/providers/AppProvider';
export default function App() {
const { user, isConnected } = useAppState();
const { connect, disconnect } = useAppDispatch();
return (
<Show when={isConnected} fallback={<button onClick={connect}>Connect</button>}>
<button onClick={disconnect}>Disconnect</button>
<h3>Your Name: {user.name}</h3>
</Show>
)
}
This component will show a button
that should run the connect
function and update isConnected
state and make the component within <Show>
block visible but it doesn't do anything.
I verified that state is being updated by logging data of appStore in connect method.
When I change the component to depend on user.name
instead isConnected
it works
<Show when={user.name} fallback={<button onClick={connect}>Connect</button>}>
<button onClick={disconnect}>Disconnect</button>
<h3>Your Name: {user.name}</h3>
</Show>
However my app has many components depending on various data types, including boolean
that for some doesn't work in this example with SolidJS.
I'd like to know what am I doing wrong here and understand what is the best way to share state between components. I keep reading documentation and fiddling with it but this particular problem bothers me for a past few days.
Upvotes: 4
Views: 3237
Reputation: 1061
The problem here is that primitive values / variables cannot be reactive in solid. We have two ways of tracking value access: Through function calls, and through property getters/proxies (which use signals under the hood).
So, what happens when you access a store property?
const state = useAppState();
createEffect(() => {
console.log(state.isConnected)
})
In this case, the property access is occurring within the effect, so it gets tracked, and reruns when the property value updates. On the other hand, with this:
const { isConnected } = useAppState();
We are accessing the property at the top level of the component (which is untrack
ed and not reactive in solid). So even though we use this value in a context that is reactive (like the when
prop in `), we can't run any special under-the-hood tracking to set up updates.
user.name
work?The reason is that stores are deeply reactive (for primitives, objects and arrays), so
const { user } = useAppState();
Means that you are eagerly accessing the user object (so if the user property changes, you won't get updated), but the properties of the user object were not accessed yet, they only get accessed further on, in <Show when={user.name}>
, so the property access user.name
is able to be tracked.
Upvotes: 1