Skip to content

Instantly share code, notes, and snippets.

@maraisr
Created December 23, 2019 03:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maraisr/c4681874646913efc34d5867255a4612 to your computer and use it in GitHub Desktop.
Save maraisr/c4681874646913efc34d5867255a4612 to your computer and use it in GitHub Desktop.
/* eslint-disable react-hooks/rules-of-hooks */
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import { useOverdriveContext } from '../components/OverdriveProvider';
import { useTheme } from '../components/ThemeProvider';
export const useMedia = (
queries: ReadonlyArray<keyof Theme['breakpoints']>,
fallbackCase = false,
): readonly boolean[] => {
const theme = useTheme();
const { isServer } = useOverdriveContext();
if (isServer) return queries.map(() => fallbackCase);
const getQueries = useCallback(() => queries.map(media => makeQueryString(...theme.breakpoints[media])), [theme]);
const matchesInit = useMemo(() => getQueries().map(query => window.matchMedia(query).matches), [getQueries]);
const [matches, setMatches] = useState<readonly boolean[]>(matchesInit);
const handler = individualHandler(setMatches);
useLayoutEffect(() => {
let isMounted = true;
const constructedQueries = getQueries();
const removeHandlers = constructedQueries
.map((query, idx) =>
addMatchMedia(query, matches => {
isMounted && handler(matches, idx);
}));
return () => {
isMounted = false;
removeHandlers.forEach(handle => handle());
};
}, [getQueries, handler, theme]);
return matches;
};
const medias = new Map<string, { handlers: Array<(matches: boolean) => void>; matcher: MediaQueryList }>();
const globalMatchMediaHandler = (e: MediaQueryListEvent) => {
unstable_batchedUpdates(() => {
medias.get(e.media)?.handlers
.forEach(handle => {
handle(e.matches);
});
});
};
const addMatchMedia = (query: string, handler) => {
if (medias.has(query)) {
const media = medias.get(query);
media.handlers.push(handler);
medias.set(query, media);
} else {
const matcher = window.matchMedia(query);
matcher.addListener(globalMatchMediaHandler);
medias.set(query, {
matcher,
handlers: [handler],
});
}
return () => void removeMatchMedia(query, handler);
};
const removeMatchMedia = (query: string, handler) => {
const media = medias.get(query);
const newHandlers = media.handlers.filter(item => item !== handler);
if (newHandlers.length === 0) {
media.matcher.removeListener(globalMatchMediaHandler);
medias.delete(query);
return;
}
medias.set(query, {
matcher: media.matcher,
handlers: newHandlers,
});
};
const individualHandler = setMatches => (matches, index) => void setMatches(prev => {
const newColl = prev.slice();
newColl[index] = matches;
return newColl;
});
const makeQueryString = (min, max = null) => {
const hasMax = max !== null;
return `screen and (min-width: ${min}px${
hasMax ? ` and max-width: ${max - 1}px` : ''
})`;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment