React

by _sir

HTML

<div id="app"></div>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.done {
  color: rgba(0, 0, 0, 0.3);
  text-decoration: line-through;
}

input {
  margin-right: 5px;
}

React

const reducer = (state, action) => {
  if (action.type === 'RESET') {
    return action.payload;
  }
  return {
    ...state,
    [action.type]: action.payload,
  };
};

const createInitialState = (queries) => {
  const allQueries = Array.isArray(queries) ? queries : [queries];
  return allQueries.reduce(
    (accum, q) => ({
      ...accum,
      [q]: window.matchMedia(q).matches,
    }),
    {}
  );
};

/**
 * Media query string or array of media queries
 * @group Hooks
 * @param {Array|string} queries
 * @returns {boolean|Object} - If a single query is passed, returns a boolean indicating whether the query matches the current viewport. If multiple queries are passed, returns an object with the query as the key and the boolean match result as the value.
 */
const useMediaQuery = (queries) => {
  const initialState = useMemo(() => createInitialState(queries), [queries]);

  const [state, dispatch] = useReducer(reducer, initialState);
  const firstRender = useRef(true);

  useEffect(() => {
    // Don't want to reset on initialRender because we start with a valid state
    if (firstRender.current) {
      firstRender.current = false;
      return;
    }
    // Anytime the queries change though, reset the state with the correct values
    dispatch({ type: 'RESET', payload: createInitialState(queries) });
  }, [queries]);

  useEffect(() => {
    const queriesToMap = Array.isArray(queries) ? queries : [queries];
    const listeners = queriesToMap.map((query) => {
      const match = window.matchMedia(query);

      const handleMediaChange = (e) => {
        dispatch({ type: query, payload: e.matches });
      };

      match.addEventListener('change', handleMediaChange);

      // Return the match and named function for cleanup in the return function below
      return {
        match,
        handleMediaChange,
      };
    });

    return () => {
      listeners.forEach((listener) =>
       ...