I'm kind of new to SolidJS, and now I'm trying to work out how SUID's ThemeProvider differs from SolidJS's (e.g. this docs provider/context example). A simple example would be how to switch between light and dark modes from a child component. What's the proper way?
I've tried something like this so far, but it hasn't worked, I thought signal reactivity would be enough, but I think I'm misunderstanding a core concept somewhere.
If the example below is successful, then the background should switch between black and white.
import { createSignal } from "solid-js";
import { createTheme } from "@suid/material";
import { green, orange } from "@suid/material/colors";
const lightTheme = createTheme({
palette: {
mode: "light",
background: {
default: "#000",
},
}
});
const darkTheme = createTheme({
palette: {
mode: "dark",
background: {
default: "#fff",
},
}
});
const [theme, setTheme] = createSignal(darkTheme);
function toggleTheme() {
theme() === lightTheme
? setTheme(darkTheme)
: setTheme(lightTheme);
}
function ChildComponent() {
return (
<FormControlLabel
onChange={(e) => {
toggleTheme();
}}
label=""
control={<Switch />}
/>
)
}
function App() {
return (
<>
<CssBaseline />
<ThemeProvider theme={theme()}>
<ChildComponent />
</ThemeProvider>
</>
);
}
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 -
The Signals & Memos return an
Accessor<T>type which, when called, in a reactive scope causes an update. You'd think -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 is the reason it doesn't work. You see, the
SUIDlibrary 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 ofstyledutility, if noThemeProvideris used by user, a default theme is injected, otherwise, if aThemeProvideris there,useThemeis used to get that theme and create components. Same is the case withSUID, it also makes use ofuseThemeunder the hood, for creating the components, which is nothing but destructuring like so -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
useThemeinside 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 theuseContexthook. 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
themesignal 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.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!
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
createMemoreactive primitive and this is the approach we'll take -Now it works! We finally have our theming!
The
createMemoreactive 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.No, you can't. Reason being, if you use the
createMemolike that, you'll get athemeobject that's memoized and since theuseContexthook passes the data as is, the theme object will be destructured like before byuseThemeand we'll lose reactivity.The reason why this approach works is, when the
themeModesignal changes, thepaletteis created again, and with that, thethemevariable 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.