Feature/hidden mediaquery optimization#1362
Feature/hidden mediaquery optimization#1362Sbragul26 wants to merge 4 commits intolayer5io:masterfrom
Conversation
d219a42 to
541bd04
Compare
Signed-off-by: Sbragul26 <sbragul26@gmail.com>
541bd04 to
c543d51
Compare
There was a problem hiding this comment.
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
ifblocks with a single loop over aBREAKPOINTSconstant array, computing only needed media query conditions. - Wrapped media query computation in
useMemoto avoid recomputation unless relevant props ortheme.breakpointschange. - Extracted
BREAKPOINTSconstant andextractConditionhelper to module scope for reuse and clarity.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/base/Hidden/Hidden.tsx
Outdated
| 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]); |
There was a problem hiding this comment.
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.
|
@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>
src/base/Hidden/Hidden.tsx
Outdated
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
src/base/Hidden/Hidden.tsx
Outdated
| 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. |
There was a problem hiding this comment.
onlyKey is order-sensitive, which defeats memoization for reordered arrays.
There was a problem hiding this comment.
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 ?? '';
Signed-off-by: Sbragul26 <sbragul26@gmail.com>
There was a problem hiding this comment.
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.
This PR fixes #1352 by refactoring the
Hiddencomponent to use media queries more efficiently.Changes included:
only,*Up, and*Downprops.upPropsanddownPropsinsideuseMemoto prevent unnecessary work on every render and fix stale closure issues.ifblocks with a loop over BREAKPOINTS for cleaner, more maintainable code.useMemoto avoid recomputation unless relevant props ortheme.breakpointschange.@media not allwhen no props are provided.Signed commits