From 3947016bb7ed5b71146b41c76b9a85987d36740f Mon Sep 17 00:00:00 2001 From: Bart Nagel Date: Sat, 5 Dec 2020 02:29:53 -0800 Subject: [PATCH] fix: rewrite useBreakpoint (#862) --- README.md | 6 ++++-- react-emotion/index.d.ts | 2 +- react-emotion/index.js | 37 +++++++++++++++++++++++++++---------- react-styled/index.d.ts | 2 +- react-styled/index.js | 37 +++++++++++++++++++++++++++---------- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1b99dfd4..38bf2179 100644 --- a/README.md +++ b/README.md @@ -275,9 +275,11 @@ only('md') => '@media (min-width: 768px) and (max-width: 991.98px) { ... }' /** * @param {function} up | down | between | only * - * @return {boolean} is the target breakpoint + * @return {(boolean|null)} `true` if currently matching the given query, + * `false` if not, and `null` if unknown (such as + * during server-side rendering) */ -useBreakpoint(up('md')) => boolean +useBreakpoint(up('md')) => boolean | null ``` ## Other diff --git a/react-emotion/index.d.ts b/react-emotion/index.d.ts index 014ca244..86b9b723 100644 --- a/react-emotion/index.d.ts +++ b/react-emotion/index.d.ts @@ -1 +1 @@ -export declare function useBreakpoint(breakpoint: Function): boolean; +export declare function useBreakpoint(breakpoint: Function): boolean | null; diff --git a/react-emotion/index.js b/react-emotion/index.js index dd263a28..27561f70 100644 --- a/react-emotion/index.js +++ b/react-emotion/index.js @@ -1,24 +1,41 @@ -const { useState, useEffect } = require('react'); +const { useState, useEffect, useCallback } = require('react'); const { useTheme } = require('@emotion/react'); const useBreakpoint = (breakpoint) => { + // Get the media query to match const query = breakpoint({ theme: useTheme(), - }).replace(/^@media/, ''); + }).replace(/^@media\s*/, ''); - const mq = window.matchMedia(query); - const [isBreak, setIsBreak] = useState(mq.matches); + // Keep track of current media query match state; + // initialize this to the current match state if possible, + // or to null (to mean "indeterminate" if the `window` object isn't available) + const [isBreak, setIsBreak] = useState( + typeof window !== 'undefined' ? window.matchMedia(query).matches : null + ); + // Handler for the media query change event + const handleChange = useCallback((event) => { + setIsBreak(event.matches); + }, []); + + // Set up a media query matcher on mount and if the query changes useEffect(() => { - const handleResize = () => { - setIsBreak(window.matchMedia(query).matches); - }; + const mq = window.matchMedia(query); - window.addEventListener('resize', handleResize); + // Ensure the correct value is set in state as soon as possible + setIsBreak(mq.matches); - return () => window.removeEventListener('resize', handleResize); - }, [query]); + // Update the state whenever the media query match state changes + mq.addEventListener('change', handleChange); + + // Clean up on unmount and if the query changes + return function cleanup() { + mq.removeEventListener('change', handleChange); + }; + }, [query, handleChange]); + // Return the current match state return isBreak; }; diff --git a/react-styled/index.d.ts b/react-styled/index.d.ts index 014ca244..86b9b723 100644 --- a/react-styled/index.d.ts +++ b/react-styled/index.d.ts @@ -1 +1 @@ -export declare function useBreakpoint(breakpoint: Function): boolean; +export declare function useBreakpoint(breakpoint: Function): boolean | null; diff --git a/react-styled/index.js b/react-styled/index.js index 1fdd6e4d..e6a264eb 100644 --- a/react-styled/index.js +++ b/react-styled/index.js @@ -1,24 +1,41 @@ -const { useState, useEffect } = require('react'); +const { useState, useEffect, useCallback } = require('react'); const { useTheme } = require('styled-components'); const useBreakpoint = (breakpoint) => { + // Get the media query to match const query = breakpoint({ theme: useTheme(), - }).replace(/^@media/, ''); + }).replace(/^@media\s*/, ''); - const mq = window.matchMedia(query); - const [isBreak, setIsBreak] = useState(mq.matches); + // Keep track of current media query match state; + // initialize this to the current match state if possible, + // or to null (to mean "indeterminate" if the `window` object isn't available) + const [isBreak, setIsBreak] = useState( + typeof window !== 'undefined' ? window.matchMedia(query).matches : null + ); + // Handler for the media query change event + const handleChange = useCallback((event) => { + setIsBreak(event.matches); + }, []); + + // Set up a media query matcher on mount and if the query changes useEffect(() => { - const handleResize = () => { - setIsBreak(window.matchMedia(query).matches); - }; + const mq = window.matchMedia(query); - window.addEventListener('resize', handleResize); + // Ensure the correct value is set in state as soon as possible + setIsBreak(mq.matches); - return () => window.removeEventListener('resize', handleResize); - }, [query]); + // Update the state whenever the media query match state changes + mq.addEventListener('change', handleChange); + + // Clean up on unmount and if the query changes + return function cleanup() { + mq.removeEventListener('change', handleChange); + }; + }, [query, handleChange]); + // Return the current match state return isBreak; };