Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use sorted breakpoints in useBreakpoint #5576

Merged
merged 3 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fuzzy-zoos-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chakra-ui/media-query": patch
---

Fixed an issue where the hook `useBreakpoint` did not work as expected with
custom breakpoints
6 changes: 6 additions & 0 deletions .changeset/large-fireants-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chakra-ui/utils": patch
---

Fixed an issue where `queryString()` created invalid media queries when min and
max were set.
91 changes: 0 additions & 91 deletions packages/media-query/src/create-media-query.ts

This file was deleted.

114 changes: 43 additions & 71 deletions packages/media-query/src/use-breakpoint.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import React from "react"
import { useEnvironment } from "@chakra-ui/react-env"
import { useTheme } from "@chakra-ui/system"
import React from "react"
import createMediaQueries from "./create-media-query"

interface Listener {
mediaQuery: MediaQueryList
handleChange: () => void
}

export interface Breakpoint {
breakpoint: string
maxWidth?: string
minWidth: string
}

/**
* React hook used to get the current responsive media breakpoint.
Expand All @@ -24,89 +12,73 @@ export interface Breakpoint {
* to get the default breakpoint value from the user-agent
*/
export function useBreakpoint(defaultBreakpoint?: string) {
const { breakpoints } = useTheme()
const { __breakpoints } = useTheme()
const env = useEnvironment()

const mediaQueries = React.useMemo(
() => createMediaQueries({ base: "0px", ...breakpoints }),
[breakpoints],
const queries = React.useMemo(
() =>
__breakpoints?.details.map(({ minMaxQuery, breakpoint }) => ({
breakpoint,
query: minMaxQuery.replace("@media screen and ", ""),
})) ?? [],
[__breakpoints],
)

const [currentBreakpoint, setCurrentBreakpoint] = React.useState(() => {
if (env.window.matchMedia) {
let maxBreakpoint
mediaQueries.forEach(({ query, ...breakpoint }) => {
const mediaQuery = env.window.matchMedia(query)
if (mediaQuery.matches) {
maxBreakpoint = breakpoint
}
})
if (maxBreakpoint) {
return maxBreakpoint
}
// set correct breakpoint on first render
const matchingBreakpointDetail = queries.find(
({ query }) => env.window.matchMedia(query).matches,
)
return matchingBreakpointDetail?.breakpoint
}

if (!defaultBreakpoint) {
return undefined
}

const mediaQuery = mediaQueries.find(
({ breakpoint }) => breakpoint === defaultBreakpoint,
)

if (mediaQuery) {
const { query, ...breakpoint } = mediaQuery
return breakpoint
if (defaultBreakpoint) {
// use fallback if available
const fallbackBreakpointDetail = queries.find(
({ breakpoint }) => breakpoint === defaultBreakpoint,
)
return fallbackBreakpointDetail?.breakpoint
}

return undefined
})

const current = currentBreakpoint?.breakpoint
React.useEffect(() => {
const allUnregisterFns = queries.map(({ breakpoint, query }) => {
const mediaQueryList = env.window.matchMedia(query)

const update = React.useCallback(
(query: MediaQueryList, breakpoint: Breakpoint) => {
if (query.matches && current !== breakpoint.breakpoint) {
if (mediaQueryList.matches) {
setCurrentBreakpoint(breakpoint)
}
},
[current],
)

React.useEffect(() => {
const listeners = new Set<Listener>()

mediaQueries.forEach(({ query, ...breakpoint }) => {
const mediaQuery = env.window.matchMedia(query)

// trigger an initial update to determine media query
update(mediaQuery, breakpoint)

const handleChange = () => {
update(mediaQuery, breakpoint)
const handleChange = (ev: MediaQueryListEvent) => {
if (ev.matches) {
setCurrentBreakpoint(breakpoint)
}
}

// add media query-listener
mediaQuery.addListener(handleChange)

// push the media query list handleChange
// so we can use it to remove Listener
listeners.add({ mediaQuery, handleChange })
// add media query listener
if (typeof mediaQueryList.addEventListener === "function") {
mediaQueryList.addEventListener("change", handleChange)
} else {
mediaQueryList.addListener(handleChange)
}

// return unregister fn
return () => {
// clean up 1
mediaQuery.removeListener(handleChange)
if (typeof mediaQueryList.removeEventListener === "function") {
mediaQueryList.removeEventListener("change", handleChange)
} else {
mediaQueryList.removeListener(handleChange)
}
}
})

return () => {
// clean up 2: for safety
listeners.forEach(({ mediaQuery, handleChange }) => {
mediaQuery.removeListener(handleChange)
})
listeners.clear()
allUnregisterFns.forEach((unregister) => unregister())
}
}, [mediaQueries, breakpoints, update, env.window])
}, [queries, __breakpoints, env.window])

return current
return currentBreakpoint
}
111 changes: 0 additions & 111 deletions packages/media-query/tests/create-media-query.test.ts

This file was deleted.

7 changes: 3 additions & 4 deletions packages/utils/src/breakpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ function subtract(value: string) {
}

function queryString(min: string | null, max?: string) {
const query = []
const query = ["@media screen"]

if (min) query.push(`@media screen and (min-width: ${px(min)})`)
if (query.length > 0 && max) query.push("and")
if (max) query.push(`@media screen and (max-width: ${px(max)})`)
if (min) query.push("and", `(min-width: ${px(min)})`)
if (max) query.push("and", `(max-width: ${px(max)})`)

return query.join(" ")
}
Expand Down