-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
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
Add useBreakpointValue hook #23885
Comments
this will be really useful! |
@atnpcg Regarding the implementation, we have been using useMediaQuery in https://github.com/mui-org/material-ui/blob/cef32a188d799618b4e0b1fe7b5ef201dbc85d6d/packages/material-ui/src/withWidth/withWidth.js#L54 instead of using It feels like it would be more efficient to rollback, leverage |
What are the use cases for this hook? I don't consider the initial primer a valid use case. Why would you want to increase elevation depending on the breakpoint? That's not what you should use elevation for. |
The use case is to bridge the gap between any JavaScript behavior and the CSS utility breakpoint API. Regarding the example, it was meant to demonstrate the API proposal. I think that the specific example (AppBar elevation) is irrelevant to the motivation for introducing the API. I have used it because it was raised specifically by this user: #15561 (comment). It's likely a design requirement for his project. Maybe a better example would have been: function MyApp() {
const variant = useBreakpointValue({ xs: 'temporary', md: 'permanent' });
return <Drawer variant={variant}>;
} |
That's not a use case. It's the exact opposite of a use case because it's obvious that a solution already exist in the form of CSS. Why this needs to be transferred to JS is something you should be able to provide.
A motivation for a solution is absolutely relevant. There's no point in working on a problem if you're unable to describe the problem at a higher level. Code should not be written just to exist. |
What about changing This is something Chakra supports out of the box. The general point here is that currently in MUI it is difficult to make some values responsive, #6140 was a step in the right direction, but it only applies to the Anyway heres a quick one I created for my own use - has not been tested in production to any great extent. export const useBreakpointValue = <TValue>(
values: {
[key in Breakpoint]?: TValue;
},
) => {
const matches = {
xs: useMediaQuery(theme.breakpoints.up(`xs`)),
sm: useMediaQuery(theme.breakpoints.up(`sm`)),
md: useMediaQuery(theme.breakpoints.up(`md`)),
lg: useMediaQuery(theme.breakpoints.up(`lg`)),
xl: useMediaQuery(theme.breakpoints.up(`xl`)),
};
const validBreakpoints = Object.entries(matches)
.filter(
([breakpoint, isMatch]) =>
Object.keys(values).includes(breakpoint) && isMatch,
)
.map(([key]) => key);
const largestBreakpoint = validBreakpoints.pop();
if (!largestBreakpoint) {
return values[0];
}
return values[largestBreakpoint];
}; |
I had the same use case of changing component properties based on the current breakpoint. |
To get the largest matching breakpoint, I use the following:
|
The problem with this approach is that useMediaQuery hook is called 6 times on each rendering. Would be awesome if there was a way to listen to some property to only call when needed (so we could add debounce to it). |
@caio-borghi-yapi yes you are right, and I have actually noticed performance issues with it. so I would not recommend my hook for production |
Do you have any ideia if there is a good way to set the currentBreakpoint without causing many rerenders? A property from useTheme to listen to would be great. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Btw, it would also be great to have a basic hook which does not return a value from an object map, but just the breakpoint value as string and/or index:
|
I couldn't find anything in MUI to help, so wrote my own and used a 3rd party lib to ensure there aren't going to be x number of instances and listeners. Also mapped over the breakpoints (instead of hard coding them) as the number of breakpoints could change per project. import { useTheme } from "@mui/material";
import { Breakpoint } from "@mui/system";
import { useEffect, useState } from "react";
import { singletonHook } from "react-singleton-hook";
// https://github.com/Light-Keeper/react-singleton-hook/issues/406#issuecomment-962282765
// eslint-disable-next-line no-underscore-dangle
export function _useCurrentBreakpoint(): Breakpoint {
const globalTheme = useTheme();
const mqs: [Breakpoint, string][] = globalTheme.breakpoints.keys.map(
(key, index, breakpoints) => {
let mq = "";
if (index === breakpoints.length - 1) {
mq = globalTheme.breakpoints.up(key);
} else {
mq = globalTheme.breakpoints.between(key, breakpoints[index + 1]);
}
return [key, mq.replace(/^@media( ?)/m, "")];
}
);
const [currentBreakpoint, setCurrentBreakpoint] = useState<Breakpoint>(() => {
const bp = mqs.find(([, mq]) => window.matchMedia(mq).matches);
return bp ? bp[0] : "xs";
});
useEffect(() => {
function handleCurrentBreakpointChange(
key: Breakpoint,
e: MediaQueryListEvent
) {
if (e.matches) {
setCurrentBreakpoint(key);
}
}
const handlers: [string, (e: MediaQueryListEvent) => void][] = mqs.map(
([key, mq]) => {
const handler = (e: MediaQueryListEvent) =>
handleCurrentBreakpointChange(key, e);
return [mq, handler];
}
);
handlers.forEach(([mq, handler]) => {
window.matchMedia(mq).addEventListener("change", handler);
});
return () => {
handlers.forEach(([mq, handler]) => {
window.matchMedia(mq).removeEventListener("change", handler);
});
};
}, [mqs]);
return currentBreakpoint;
}
const useCurrentBreakpoint = singletonHook("xs", _useCurrentBreakpoint);
export { useCurrentBreakpoint }; |
Summary 💡
Provide a JavaScript API as a counterpart to the CSS sx breakpoint's API.
Examples 🌈
Internally,
useBreakpointValue
would useuseMediaQuery
oruseBreakpoint
(probably better).Motivation 🔦
We have started to surface the need for it in #15561 (comment). The hook can be used anytime a value needs to be determined based on the breakpoints. This comment #17000 (comment) triggered the idea around opening this issue. I think that this hook is best for props that are already dependent on JavaScript logic.
One could argue that many of the CSS properties could be made responsive, e.g. the
color
prop. However, it might be overkill, we could use this hook as an escape hatch.We have been discussing the idea to remove the
<HiddenCss>
helper as the Box covers most of the usage thanks to the sx's display feature. I think that this JS counterpart can solve the<HiddenJs>
part of the migration issue: #19704 (comment).Benchmarks
The text was updated successfully, but these errors were encountered: