question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

useMediaQuery not working -- using stale state value

See original GitHub issue

Description

When I resize the screen to be smaller, then larger, it continues to think itā€™s small

Link to Reproduction

šŸ„²

Description of the bug

At this line https://github.com/chakra-ui/chakra-ui/blob/a0de717c98e574e73f8a444f576fc738907230ab/packages/media-query/src/use-media-query.ts#L38

Weā€™re comparing matches to currentMatches, this is where the bug is. Because we do not supply matches as a dependency for the useSafeLayoutEffects in https://github.com/chakra-ui/chakra-ui/blob/a0de717c98e574e73f8a444f576fc738907230ab/packages/media-query/src/use-media-query.ts#L53

So we keep a reference to a function that has a stale reference to matches, therefore it keeps using the original value instead of the latest one.

Thereā€™s two ways to fix it:

  • Simply add matches to the dependencies array for the useSafeLayoutEffect hook
  • Use a ref as a secondary storage measure to store matches and use that in comparison, useRef returns a stable value, so we do not need to add it to the dependencies.

I chose the latter in my patch because I did not read about useSafeLayoutEffect and do not know what excessive re-cals in it will cause

Patch

Hereā€™s the patched one Iā€™m using, the only change is the use of useRef

import { useEnvironment } from '@chakra-ui/react-env'
import { isBrowser } from '@chakra-ui/utils'
import * as React from 'react'

const useSafeLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect

// NOTE: This hook is a PATCHED version of the "useMediaQuery" hook
// that ships with @chakra-ui/react. The difference/fix we have is that
// we're storing the "matches" value in a ref for comparison, therefore
// fixing a bug by not using stale value generated at first-run time.

/**
 * React hook that tracks state of a CSS media query
 *
 * @param query the media query to match
 */
export default function useMediaQuery(query: string | string[]): boolean[] {
    const env = useEnvironment()
    const queries = Array.isArray(query) ? query : [query]
    const isSupported = isBrowser && 'matchMedia' in env.window

    const [matches, setMatches] = React.useState(
        queries.map(queryItem => (isSupported ? !!env.window.matchMedia(queryItem).matches : false))
    )
    const matchesRef = React.useRef(matches)

    useSafeLayoutEffect(() => {
        if (!isSupported) return undefined

        const mediaQueryList = queries.map(queryItem => env.window.matchMedia(queryItem))

        const listenerList = mediaQueryList.map(() => {
            const listener = () => {
                const isEqual = (prev: boolean[], curr: boolean[]) =>
                    prev.length === curr.length && prev.every((elem, idx) => elem === curr[idx])

                const currentMatches = mediaQueryList.map(mediaQuery => mediaQuery.matches)

                if (!isEqual(matchesRef.current, currentMatches)) {
                    matchesRef.current = currentMatches
                    setMatches(currentMatches)
                }
            }

            env.window.addEventListener('resize', listener)

            return listener
        })

        return () => {
            mediaQueryList.forEach((_, index) => {
                env.window.removeEventListener('resize', listenerList[index])
            })
        }
    }, [query])

    return matches
}

Chakra UI Version

1.7.1

Browser

Google Chrome 95

Operating System

  • macOS
  • Windows
  • Linux

Additional Information

Let me know if I can explain this further, hope the patch is helpful!

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
SSTPIERRE2commented, Dec 6, 2021

Hi @segunadebayo is this fixed in the latest version? Sorry I couldnā€™t find it in the release notes

2reactions
primos63commented, Nov 22, 2021

Iā€™m including my CodeSandbox so that you can verify that what I propose and what you have proposed produce the same results (you probably did that already). Unless you have an objection, I would favor going with updating the dependency list to what I originally intended it to be.

Hey, thanks for fix. Shouldnā€™t removeEventListener remove change event in useMediaQuery.js instead of resize?

E.g.

mediaQueryList.forEach((queryList, index) => {
  queryList.removeEventListener("change", listenerList[index]);
}); 

Thanks. It should be mediaQueryList.removeEventListener. I spent the weekend going over the code and creating a mock so that it could have some unit tests. What Iā€™ll be dropping today should be more performant. The listener will use the MQL event generated so that it only responds to actual changes rather than window resizing. I also removed the useEffect dependency list as thereā€™s no need for it with my changes. Having a dependency caused the listeners to unload and reload. That cause events to be missed. Thereā€™s no need for unloading unless the component using the hook unloads. Quite a few hours spent researching, writing and debugging.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can't perform a React state update with useMediaQuery
I'm trying to use useMediaQuery with NextJS to conditionally render a background image, but i get "Can't perform a React state update on...
Read more >
React Hooks and stale state
If you're using React Hooks you might encounter a scenario where your useState hook doesn't appear to be updating the value. This can...
Read more >
Handling the React server hydration mismatch error
The real problem is that our UI is ā€œstale.ā€ React doesn't update the UI to match the props that were rendered differently by...
Read more >
react-responsive - npm
Start using react-responsive in your project by running `npm i react-responsive` ... import React from 'react' import { useMediaQuery } fromĀ ...
Read more >
CSS Media Min-Width & Max-Width Queries - How They Work
So if the email is opened on an iPhone 5S with a screen width of 320px, the media query will not trigger and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found