TLDR;
The way SolidJS works is pretty different from React. It tracks updates or reactivity based on function calls essentially, and we ve to make use of reactive primitives in SolidJS to be able to make sure that the UI is reactive. So, you ll need to create your theme by using a reactive primitive.
Setting the Baseline
The SolidJS s guide to reactivity outlines all the types of reactive primitives and how they work.
Reactivity in SolidJS only works if any reactive primitive is accessed in the a reactive scope, so to say, which is, any other reactive primitive (using a signal inside of a effect for instance) or inside of the JSX code. If this is the case, re-render occurs, and updates are reflected. To quote SolidJS docs -
- All reactivity is tracked from function calls whether directly or hidden beneath getter/proxy and triggered by property access. This means where you access properties on reactive objects is important.
- Components and callbacks from control flows are not tracking scopes and only execute once. This means destructuring or doing logic top-level in your components will not re-execute. You must access these Signals, Stores, and props from within other reactive primitives or the JSX for that part of the code to re-evaluate.
The Signals & Memos return an Accessor<T>
type which, when called, in a reactive scope causes an update. You d think -
But I m using the signal inside of the JSX (in my example), and that s a reactive context, so what s wrong?
Yes, you re using it like so, and it s not your fault that it doesn t work, it s SUID
s fault. Remember the rule about destructuring from the docs -
This means destructuring or doing logic top-level in your components will not re-execute.
This is the reason it doesn t work. You see, the SUID
library is just MUI using SolidJS instead of React. The architecture is same as MUI. To inject styles into all components, the MUI library makes use of styled
utility, if no ThemeProvider
is used by user, a default theme is injected, otherwise, if a ThemeProvider
is there, useTheme
is used to get that theme and create components. Same is the case with SUID
, it also makes use of useTheme
under the hood, for creating the components, which is nothing but destructuring like so -
const theme = useTheme();
So,the theme is getting updated, but there are no updates. Let s move on to debugging step by step.
Debugging: Understand What s Happening
In your example, you re trying to do it way it s done in React. I don t blame you. Let s use your example to compare with React to see what s really happening.
Here s an example of React MUI, and here s an example of SolidJS with SUID. When we click on the button in front of "From Signal" the handler fires in both the examples. However, the behavior isn t same. What s missing?
We see that in MUI, the theme state reflects properly on buttons and the theme mode reflects properly as well, however, in SolidJS the value of theme mode reflects properly from the button using the signal value but the button using theme from useTheme
inside of the context, doesn t seem to be able to get it right. But it actually is getting the correct theme, SolidJS passes the objects as is through the useContext
hook. This is happening because of destructuring, the UI doesn t re-render. Let me prove that to you.
Let s change some piece of code in our SolidJS example, to make it look like this, we ll pass the theme
signal wrapped inside of an array instead of passing it plainly in the provider -- with that we ll need to make use of vanilla html components, because with an array type of theme, the lib components will error from not being able to access the theme.
What? Both the buttons now reflect correct theme mode in SolidJS as well?
Yes, because this time, we re destructuring an array, and it has the reference to the signal, so we just create a wrapper to get that value, and it works!
Enough of all this now, how do I fix the theming then?
Let s move on to that.
How to implement theming?
As mentioned at the start, we ll need to make use of reactive primitives, and as already seen, signals don t really help, so, we ll make use of the createMemo
reactive primitive and this is the approach we ll take -
import {
createTheme,
ThemeProvider,
Typography,
useTheme,
} from @suid/material ;
import Button from @suid/material/Button ;
import { createPalette } from @suid/material/styles/createPalette ;
import { createSignal, createMemo, createEffect } from solid-js ;
function Btn(props: any) {
const theme = useTheme();
return (
<div
style={{
display: flex ,
gap: 1rem ,
alignItems: center ,
margin: 10px ,
}}
>
<Typography>From Context: </Typography>
<Button variant="contained" color="success">
{theme.palette.mode}
</Button>
</div>
);
}
export default function Counter(props: any) {
const [themeMode, setThemeMode] = createSignal< light | dark >( light );
const palette = createMemo(() => {
return createPalette({ mode: themeMode() });
});
const theme = createTheme({ palette: palette });
function onClick() {
setThemeMode((prev) => (prev === light ? dark : light ));
}
return (
<ThemeProvider theme={theme}>
<div
style={{
display: flex ,
gap: 1rem ,
alignItems: center ,
margin: 10px ,
}}
>
<Typography>From Signal: </Typography>
<Button variant="contained" color="success" onClick={onClick}>
{themeMode()}
</Button>
</div>
<Btn />
<div
style={{
display: flex ,
gap: 1rem ,
alignItems: center ,
justifyContent: center ,
margin: 15px 50px ,
fontWeight: bold ,
}}
>
SolidJS
</div>
</ThemeProvider>
);
}
Now it works! We finally have our theming!
Wait, why do we have to do it like this?
The createMemo
reactive primitive is a signal and an effect at the same time. It outputs a reactive value, and in all the functions where that reactive value is used, the function is registered on the SolidJS s runtime internal stack as a subscriber.
So, can t I just use the createTheme
inside of the createMemo
?
No, you can t. Reason being, if you use the createMemo
like that, you ll get a theme
object that s memoized and since the useContext
hook passes the data as is, the theme object will be destructured like before by useTheme
and we ll lose reactivity.
The reason why this approach works is, when the themeMode
signal changes, the palette
is created again, and with that, the theme
variable is updated. After all that s done and the nodes in the tree are there, an effect is run to update the tree, and hence we see the theming work.