diff --git a/.changeset/witty-spiders-explode.md b/.changeset/witty-spiders-explode.md new file mode 100644 index 00000000..34fea61c --- /dev/null +++ b/.changeset/witty-spiders-explode.md @@ -0,0 +1,5 @@ +--- +"usehooks-ts": minor +--- + +Fix hydration issues in both useScreen and useMediaQuery (Fixes #394, thanks to @bryantcodesart) diff --git a/packages/usehooks-ts/src/useMediaQuery/useMediaQuery.ts b/packages/usehooks-ts/src/useMediaQuery/useMediaQuery.ts index c36682b9..29283e35 100644 --- a/packages/usehooks-ts/src/useMediaQuery/useMediaQuery.ts +++ b/packages/usehooks-ts/src/useMediaQuery/useMediaQuery.ts @@ -1,16 +1,11 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' -const getMatches = (query: string): boolean => { - // Prevents SSR issues - if (typeof window !== 'undefined') { - return window.matchMedia(query).matches - } - return false -} +import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect' /** * Custom hook for tracking the state of a media query. * @param {string} query - The media query to track. + * @param {?boolean} [defaultValue] - The default value to return if the hook is being run on the server (default is `false`). * @returns {boolean} The current state of the media query (true if the query matches, false otherwise). * @see [Documentation](https://usehooks-ts.com/react-hook/use-media-query) * @see [MDN Match Media](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) @@ -18,15 +13,25 @@ const getMatches = (query: string): boolean => { * const isSmallScreen = useMediaQuery('(max-width: 600px)'); * // Use `isSmallScreen` to conditionally apply styles or logic based on the screen size. */ -export function useMediaQuery(query: string): boolean { - const [matches, setMatches] = useState(getMatches(query)) - - useEffect(() => { - /** Handles the change event of the media query. */ - function handleChange() { - setMatches(getMatches(query)) +export function useMediaQuery( + query: string, + defaultValue: boolean = false, +): boolean { + const [matches, setMatches] = useState(defaultValue) + + const getMatches = (query: string): boolean => { + if (typeof window !== 'undefined') { + return window.matchMedia(query).matches } + return defaultValue + } + + /** Handles the change event of the media query. */ + function handleChange() { + setMatches(getMatches(query)) + } + useIsomorphicLayoutEffect(() => { const matchMedia = window.matchMedia(query) // Triggered at the first client-side load and if query changes diff --git a/packages/usehooks-ts/src/useScreen/useScreen.ts b/packages/usehooks-ts/src/useScreen/useScreen.ts index 2d739821..af43d9f0 100644 --- a/packages/usehooks-ts/src/useScreen/useScreen.ts +++ b/packages/usehooks-ts/src/useScreen/useScreen.ts @@ -11,27 +11,27 @@ import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect' * const currentScreen = useScreen(); * // Access properties of the current screen, such as width and height. */ -export function useScreen() { +export function useScreen(): Screen | undefined { const getScreen = () => { - if (typeof window !== 'undefined' && window.screen) { + if (typeof window !== 'undefined') { return window.screen } return undefined } - const [screen, setScreen] = useState(getScreen()) + const [screen, setScreen] = useState(undefined) /** Handles the resize event of the window. */ function handleSize() { setScreen(getScreen()) } + // TODO: Prefer incoming useResizeObserver hook useEventListener('resize', handleSize) // Set size at the first client-side load useIsomorphicLayoutEffect(() => { handleSize() - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return screen