Skip to content

Feature/hidden mediaquery optimization#1362

Open
Sbragul26 wants to merge 4 commits intolayer5io:masterfrom
Sbragul26:feature/hidden-mediaquery-optimization
Open

Feature/hidden mediaquery optimization#1362
Sbragul26 wants to merge 4 commits intolayer5io:masterfrom
Sbragul26:feature/hidden-mediaquery-optimization

Conversation

@Sbragul26
Copy link
Contributor

This PR fixes #1352 by refactoring the Hidden component to use media queries more efficiently.

Changes included:

  • Compute a single combined media query using the provided only, *Up, and *Down props.
  • Moved upProps and downProps inside useMemo to prevent unnecessary work on every render and fix stale closure issues.
  • Replaced repetitive if blocks with a loop over BREAKPOINTS for cleaner, more maintainable code.
  • Memoized media query computation with useMemo to avoid recomputation unless relevant props or theme.breakpoints change.
  • Fallback to @media not all when no props are provided.

Signed commits

  • Yes, I signed my commits.

@Sbragul26 Sbragul26 force-pushed the feature/hidden-mediaquery-optimization branch from d219a42 to 541bd04 Compare March 6, 2026 17:22
Signed-off-by: Sbragul26 <sbragul26@gmail.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #1352 by refactoring the Hidden component to compute a single combined media query more efficiently, using useMemo and a loop over breakpoints instead of repetitive if blocks.

Changes:

  • Replaced repetitive per-breakpoint if blocks with a single loop over a BREAKPOINTS constant array, computing only needed media query conditions.
  • Wrapped media query computation in useMemo to avoid recomputation unless relevant props or theme.breakpoints change.
  • Extracted BREAKPOINTS constant and extractCondition helper to module scope for reuse and clarity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const mediaQuery =
conditions.length > 0 ? `@media ${conditions.join(', ')}` : '@media not all';
return conditions.length > 0 ? `@media ${conditions.join(', ')}` : '@media not all';
}, [only, xsUp, smUp, mdUp, lgUp, xlUp, xsDown, smDown, mdDown, lgDown, xlDown, theme.breakpoints]);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

When only is passed as an array literal (e.g., only={['xs', 'md']}), which is the typical usage pattern, a new array reference is created on every parent render. Since only is used directly as a useMemo dependency, this causes the memo to recompute on every render, defeating the purpose of the memoization.

Consider spreading the array into the dependency list (e.g., using JSON.stringify(only) or only?.toString() as the dependency), or converting only to a stable representation before using it as a dependency.

Copilot uses AI. Check for mistakes.
@Bhumikagarggg
Copy link
Contributor

@Sbragul26 Thank you for your contribution! Let's discuss this during the website call today at 6:30 PM IST | 7 AM CST Add it as an agenda item to the meeting minutes, if you would 🙂

Signed-off-by: Sbragul26 <sbragul26@gmail.com>
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
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
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

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
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
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 ?? '';

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use "up" and "down" props for media queries pervasively

5 participants