English 中文(简体)
What s the proper way to switch between dark and light modes on SolidJS + SUID?
原标题:
The bounty expires in 3 days. Answers to this question are eligible for a +100 reputation bounty. Philippe Fanaro is looking for a canonical answer:
It needs to successfully be able to toggle between themes, using SUID s ThemeProvider, and preferably explain why and how it differs from regular SolidJS signals or contexts.

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 -

  1. 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.
  2. 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?

Solid debug MUI debug

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.

debug2

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.

Solid does not have a theme provider but provides context API to create a theme provider in this case ThemeProvider.

ThemeProvider is component that passes a theme data down to its children. It is not only its direct children but an children that lives under it.

For the example, data is the value return by theme() signal.

FormControlLabelunder ChildComponent provides a way to toggle dark and light themes by swapping the active theme through setTheme.

Any component that has access to setThemecan set a new theme.

A new theme can be created via createTheme. It provides minimum requirements for a theme value. A theme is an object with a value called palette that has mode etc.

What you are trying to do is to use a context value to pass data, a JavaScript object with CSS values, to downstream components. Since code has no issue but you don t know how it works, it is best for you to learn how solid works and how context API fits into its reactive system.

You can check out this answer for how to use Context API: https://stackoverflow.com/a/74492899/7134134





相关问题
selected text in iframe

How to get a selected text inside a iframe. I my page i m having a iframe which is editable true. So how can i get the selected text in that iframe.

How to fire event handlers on the link using javascript

I would like to click a link in my page using javascript. I would like to Fire event handlers on the link without navigating. How can this be done? This has to work both in firefox and Internet ...

How to Add script codes before the </body> tag ASP.NET

Heres the problem, In Masterpage, the google analytics code were pasted before the end of body tag. In ASPX page, I need to generate a script (google addItem tracker) using codebehind ClientScript ...

Clipboard access using Javascript - sans Flash?

Is there a reliable way to access the client machine s clipboard using Javascript? I continue to run into permissions issues when attempting to do this. How does Google Docs do this? Do they use ...

javascript debugging question

I have a large javascript which I didn t write but I need to use it and I m slowely going trough it trying to figure out what does it do and how, I m using alert to print out what it does but now I ...

Parsing date like twitter

I ve made a little forum and I want parse the date on newest posts like twitter, you know "posted 40 minutes ago ","posted 1 hour ago"... What s the best way ? Thanx.

热门标签