Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 28 additions & 55 deletions src/base/Hidden/Hidden.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMediaQuery, useTheme } from '@mui/material';
import React from 'react';
import React, { useMemo } from 'react';

type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

Expand All @@ -16,9 +16,13 @@ export interface HiddenProps {
mdDown?: boolean;
lgDown?: boolean;
xlDown?: boolean;

}

const BREAKPOINTS: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl'];

const extractCondition = (mediaQuery: string) =>
mediaQuery.replace(/^@media\s*/i, '');

export const Hidden = ({
children,
only,
Expand All @@ -34,63 +38,32 @@ export const Hidden = ({
xlDown = false
}: HiddenProps) => {
const theme = useTheme();
const onlyValues = Array.isArray(only) ? only : only ? [only] : [];

const conditions: string[] = [];

const extractCondition = (mediaQuery: string) =>
mediaQuery.replace(/^@media\s*/i, '');

if (onlyValues.includes('xs')) {
conditions.push(extractCondition(theme.breakpoints.only('xs')));
}
if (onlyValues.includes('sm')) {
conditions.push(extractCondition(theme.breakpoints.only('sm')));
}
if (onlyValues.includes('md')) {
conditions.push(extractCondition(theme.breakpoints.only('md')));
}
if (onlyValues.includes('lg')) {
conditions.push(extractCondition(theme.breakpoints.only('lg')));
}
if (onlyValues.includes('xl')) {
conditions.push(extractCondition(theme.breakpoints.only('xl')));
}
// Serialize `only` to a stable string so that array literals passed as props
// (e.g. only={['xs', 'md']}) don't defeat memoization due to new references.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onlyKey is order-sensitive, which defeats memoization for reordered arrays.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner loop iterates BREAKPOINTS in fixed order, so ['xs', 'sm'] and ['sm', 'xs'] produce identical media queries. But their onlyKeys differ, causing unnecessary useMemo cache invalidation.

Fix: Sort before joining:

const onlyKey = Array.isArray(only) ? [...only].sort().join(',') : only ?? '';

const onlyKey = Array.isArray(only) ? only.join(',') : only ?? '';

if (xsUp) {
conditions.push(extractCondition(theme.breakpoints.up('xs')));
}
if (smUp) {
conditions.push(extractCondition(theme.breakpoints.up('sm')));
}
if (mdUp) {
conditions.push(extractCondition(theme.breakpoints.up('md')));
}
if (lgUp) {
conditions.push(extractCondition(theme.breakpoints.up('lg')));
}
if (xlUp) {
conditions.push(extractCondition(theme.breakpoints.up('xl')));
}
const mediaQuery = useMemo(() => {
const onlyValues = Array.isArray(only) ? only : only ? [only] : [];
const upProps: Record<Breakpoint, boolean> = { xs: xsUp, sm: smUp, md: mdUp, lg: lgUp, xl: xlUp };
const downProps: Record<Breakpoint, boolean> = { xs: xsDown, sm: smDown, md: mdDown, lg: lgDown, xl: xlDown };
const conditions: string[] = [];

if (xsDown) {
conditions.push(extractCondition(theme.breakpoints.down('xs')));
}
if (smDown) {
conditions.push(extractCondition(theme.breakpoints.down('sm')));
}
if (mdDown) {
conditions.push(extractCondition(theme.breakpoints.down('md')));
}
if (lgDown) {
conditions.push(extractCondition(theme.breakpoints.down('lg')));
}
if (xlDown) {
conditions.push(extractCondition(theme.breakpoints.down('xl')));
}
for (const bp of BREAKPOINTS) {
if (onlyValues.includes(bp)) {
conditions.push(extractCondition(theme.breakpoints.only(bp)));
}
if (upProps[bp]) {
conditions.push(extractCondition(theme.breakpoints.up(bp)));
}
if (downProps[bp]) {
conditions.push(extractCondition(theme.breakpoints.down(bp)));
}
}

const mediaQuery =
conditions.length > 0 ? `@media ${conditions.join(', ')}` : '@media not all';
return conditions.length > 0 ? `@media ${conditions.join(', ')}` : '@media not all';
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't suppress lint checks without an explanation.

The linter is correctly flagging that only is used inside the closure but absent from the dependency array. The substitution of onlyKey for only is actually valid (since onlyKey encodes the same logical value), but without explanation, this looks like a hidden bug to any future maintainer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better approach — eliminate the suppress entirely by computing onlyValues from onlyKey inside the memo:

const onlyKey = Array.isArray(only) ? [...only].sort().join(',') : only ?? '';

const mediaQuery = useMemo(() => {
  // Derive onlyValues from onlyKey (the stable serialization) so `only` is not
  // a closure dependency. This avoids cache busting from inline array literals.
  const onlyValues = onlyKey ? (onlyKey.split(',') as Breakpoint[]) : [];
  const upProps: Record<Breakpoint, boolean> = { xs: xsUp, sm: smUp, md: mdUp, lg: lgUp, xl: xlUp };
  const downProps: Record<Breakpoint, boolean> = { xs: xsDown, sm: smDown, md: mdDown, lg: lgDown, xl: xlDown };
  const conditions: string[] = [];

  for (const bp of BREAKPOINTS) {
    if (onlyValues.includes(bp)) conditions.push(extractCondition(theme.breakpoints.only(bp)));
    if (upProps[bp])             conditions.push(extractCondition(theme.breakpoints.up(bp)));
    if (downProps[bp])           conditions.push(extractCondition(theme.breakpoints.down(bp)));
  }

  return conditions.length > 0 ? `@media ${conditions.join(', ')}` : '@media not all';
}, [onlyKey, xsUp, smUp, mdUp, lgUp, xlUp, xsDown, smDown, mdDown, lgDown, xlDown, theme.breakpoints]);
// No eslint-disable needed — `only` is no longer referenced inside the memo

}, [onlyKey, xsUp, smUp, mdUp, lgUp, xlUp, xsDown, smDown, mdDown, lgDown, xlDown, theme.breakpoints]);

const matches = useMediaQuery(mediaQuery);

Expand Down