@react-hookz/web
Version:
React hooks done right, for browser and SSR.
79 lines (78 loc) • 2.67 kB
JavaScript
import { useCallback, useEffect, useRef } from 'react';
import { usePrevious, useRerender } from '..';
import { isBrowser } from "../util/const.js";
const queriesMap = new Map();
const querySubscribe = (query, setState) => {
let entry = queriesMap.get(query);
if (!entry) {
const mql = matchMedia(query);
const dispatchers = new Set();
const listener = () => {
dispatchers.forEach((d) => d(mql.matches));
};
if (mql.addEventListener)
mql.addEventListener('change', listener, { passive: true });
else
mql.addListener(listener);
entry = {
mql,
dispatchers,
listener,
};
queriesMap.set(query, entry);
}
entry.dispatchers.add(setState);
setState(entry.mql.matches, true);
};
const queryUnsubscribe = (query, setState) => {
const entry = queriesMap.get(query);
// else path is impossible to test in normal situation
/* istanbul ignore else */
if (entry) {
const { mql, dispatchers, listener } = entry;
dispatchers.delete(setState);
if (!dispatchers.size) {
queriesMap.delete(query);
if (mql.removeEventListener)
mql.removeEventListener('change', listener);
else
mql.removeListener(listener);
}
}
};
/**
* Tracks the state of CSS media query.
*
* Defaults to false in SSR environments
*
* @param query CSS media query to track.
* @param matchOnMount whether hook state should be fetched during effects stage instead of
* synchronous fetch. Set this parameter to `true` for SSR use-cases.
*/
export function useMediaQuery(query, matchOnMount) {
const rerender = useRerender();
const previousQuery = usePrevious(query);
const state = useRef();
const setState = useCallback((matches, initial) => {
if (state.current !== matches) {
state.current = matches;
if (!initial || matchOnMount)
rerender();
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[matchOnMount]);
// do synchronous subscription only for case we are in browser and mount match required
if (!matchOnMount && isBrowser && previousQuery !== query) {
querySubscribe(query, setState);
}
// otherwise, match should happen in effect stage
useEffect(() => {
if (matchOnMount) {
querySubscribe(query, setState);
}
return () => queryUnsubscribe(query, setState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query]);
return state.current;
}