-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[docs] Fix useStorageState regressions #41223
Conversation
Netlify deploy previewhttps://deploy-preview-41223--material-ui.netlify.app/ Bundle size reportDetails of bundle changes (Toolpad) |
packages/mui-utils/src/useLocalStorageState/useLocalStorageState.ts
Outdated
Show resolved
Hide resolved
I noticed that many of these hooks define For our components we could consider exporting a separate |
@Janpot I think I get the point but do you have an example? How I understand things work:
Which confusion?
This makes me think of #42750. I believe we need a theme.js in the server bundle, and one in the client bundle (which runs serverside for the initial request). |
Yep, seems about right. So the question would be why is e.g.
The file is marked as a server/client boundary ( 'use server'
import { Box } from "@mui/material";
export default function Home() {
return (
// This fails at runtime
<Box sx={{ background: (theme) => theme.palette.background.paper }}>
Hello
</Box>
);
} Perhaps it makes sense to make two versions of the components:
That way either the build fails (you imported something that has Just some shower thoughts though. Nothing ground-breaking, I just tend to treat types as documentation, and it would be helpful if they prevent me from writing invalid code. Haven't found any comment of the React core team on how libraries should deal with this in the ideal scenario. |
a3b7125
to
b8cec51
Compare
// If the key is deleted, value will be null then reset color scheme to the default one. | ||
if (event.key.endsWith('light')) { | ||
setColorScheme({ light: value as SupportedColorScheme | null }); | ||
if (storageWindow) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this to wrap the maximum possible scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this operating on the same key as useLocalStorageState
? Can't it just use the hook instead? I don't think the storage value should be manipulated by anything else, it will definitely lead to bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this operating on the same key as useLocalStorageState?
It does, but the TODO comments I have left in the code are to move to a point where the mode is to be handled by useCurrentColorScheme
and not useLocalStorageState
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I envisioned this was
// have these hooks available in the docs
function usePaletteModeUserPreference() {
const [mode, setMode] = useLocalStorageState('colorScheme', 'system');
return { mode, setMode };
}
function useCurrentPaletteMode() {
const { mode: userMode } = usePaletteModeUserPreference();
const systemColor = useSystemColor();
return userMode === 'system' ? systemColor : userMode;
}
// for theme provider
const paletteMode = useCurrentPaletteMode()
const theme = paletteMode === 'dark' ? darkTheme : lightTheme;
// anywhere for theme switchers
const { mode, setMode } = usePaletteModeUserPreference();
<Button active={mode === 'dark'} onClick={() => setMode('light')}>dark</Button>
<Button active={mode === 'light'} onClick={() => setMode('dark')}>light</Button>
The nice thing about useLocalStorage
is that it acts as global state, so you don't ever need to add a shared state variable somewhere and create a context for it. Just the hook works anywhere you use it, it handles state outside of React. This system worked very well for the Toolpad app theme switcher. I might be oversimplifying for the docs, I'd have to look deeper into it, there may be blockers to adopt the same way of doing a theme switcher in the core docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Janpot Yes but what I see is that the mode is a lot more integrated with MUI System once you use CSS Variables and Zero runtime than it was with Emotion (what Toolpad uses).
MUI System almost needs to completely own that logic. If it's possible to separate things then I'm 💯 for it. I had this feeling that hard to understand how things work because of how closely things are linked together but I don't really see how to simplify things. We have effectively built https://github.com/pacocoursey/next-themes.
cc @siriwatknp.
return () => media.removeListener(handler); | ||
return () => { | ||
media.removeListener(handler); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm never clear on what's the return value of functions, makes it clear.
// Early exit, nothing changed. | ||
if (currentState.systemMode === systemMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would render twice, unnecessary.
mode, | ||
systemMode, | ||
setMode, | ||
lightColorScheme, | ||
darkColorScheme, | ||
allColorSchemes, | ||
colorScheme, | ||
darkColorScheme, | ||
lightColorScheme, | ||
mode, | ||
setColorScheme, | ||
allColorSchemes, | ||
setMode, | ||
systemMode, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorted to match the dependencies, it makes it easier to find missing value.
function CssVarsProvider(props) { | ||
const { | ||
children, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency in the codebase, makes it easy to see components to other stuff.
@@ -1,5 +1,4 @@ | |||
'use client'; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convention
// No value changed | ||
if ( | ||
(!action.payload.paletteMode || action.payload.paletteMode === state.paletteMode) && | ||
(!action.payload.direction || action.payload.direction === state.direction) && | ||
(!action.payload.paletteColors || action.payload.paletteColors === state.paletteColors) | ||
) { | ||
return state; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid redundant renders
// TODO: have the value come from useColorScheme(); | ||
// paletteMode: nextPaletteMode, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explains why code is gone.
function ModeSwitcher({ md2Mode }: { md2Mode: Mode }) { | ||
const { setMode } = useColorScheme(); | ||
React.useEffect(() => { | ||
setMode(md2Mode); | ||
}, [md2Mode, setMode]); | ||
return null; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either we use different modeStorageKey
and attribute
keys between Material UI and Material UI Next or we remove this.
Signed-off-by: Jan Potoms <[email protected]>
…ling during hydration
Addresses #41096 (comment)
mode
being defined during hydration, even though it had logic to stabilize values during hydration already. I'm just rendering an empty disabled button until it's mounted, as that seems to have been the intended behavior already. This is the simplest solution to avoid us from stabilizing every possible prop during mounting.I propose we just fall back to the SSR implementation, which is a simplebrought back the try/catch to handle all the ways localStorage can be disabled.React.useState
@siriwatknp Instead of switching them in the dom, I assume it could be possible to use css vars to toggle the visibility of these two icons. I can't seem to immediately find how to do this though. Opportunity to improve the docs?
Preview: https://deploy-preview-41223--material-ui.netlify.app/