Source: layout/MediaQuery.js

import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch } from "react-redux";
import { appActions } from "../../redux";
import { processOptions } from "../../_core";

const matchQueries = (window.config.CONSTANTS.MEDIA_QUERY_SCHEMA || []).map(x => ({
  ...x,
  matchMedia: window.matchMedia(x.query)
}));
//const mediaQuery = '(max-width: 700px)';
//const mediaQueryList = window.matchMedia(mediaQuery);

/**
 * Componente que crea un contexto de variables a partir de un esquema de MediaQuery. Este componente se recomienda ponerlo en el Layout para evitar multiples consultas de MediaQuery
 * @name Reactor.Components.Layout.MediaQuery
 * @param {Array.<{schema_name1: object, schema_name2: object, schema_name3: object}>} items cada regla en el esquema, definido en window.config.CONSTANTS.MEDIA_QUERY_SCHEMA, genera una propiedad con su name, ahi se envia la data que se van a setear en el contexto cuando se cumpla un match
 * @param {string} default opcionalmente se puede dar el nombre de la regla que se va a tomar por defecto cuando no se produzca un match
 * @param {string} context Nombre del contexto que contiene o va a contener la data en caso de match o default, si se omite el contexto sera "global"
 * @param {"true"|"false"} storage Si es "true" el contexto se guarda en LocalStorage
 * @param {"true"|"false"} debug Si es "true" se mostrara en consola el resultado de las queries
 * @class
 * @example
<style>
  .div-test{
    background-color: blue;
    height: 30px;
  }
  .sm280 {
    width: 180px; 
  }
  .sm320 {
    width: 220px; 
  }
  .sm360 {
    width: 260px; 
  }
  .sm375 {
    width: 275px; 
  }
  .sm390 {
    width: 290px; 
  }
  .sm412 {
    width: 312px; 
  }
  .sm540 {
    width: 440px; 
  }   
  .sm {
    width: 476px; 
  }   
  .md {
    width: 668px; 
  }   
  .lg {
    width: 892px; 
  }   
  .lg820 {
    width: 720px; 
  }   
  .lg912 {
    width: 812px; 
  }   
  .xl {
    width: 1100px; 
  }           
  .xl1024 {
    width: 924px; 
  }  
  .xxl {
    width: 1300px; 
  }
</style>
<!-- 
  La variable rule del ejemplo solamente se usa en el ejemplo para mostrar la regla que se cumplio, 
  no tiene ninguna otra utlidad, las reglas se definen en el archivo config.
  La variable graficoWitdh podria ser util para setear la propiedad width de un chart, 
  mientras que la variable divClass una clase en un elemento div.
  Si bien el componente únicamente modifica las variables cuando se produce un cambio en el estado de las reglas, 
  y se cumple una condición. Es muy recomendable que solamente se use únicamente en los templates de Layout, 
  para evitar conflictos y generacion de eventos globales excesivos. 
-->
<media_query 
  xs280='{
    "rule": "xs280 (min-width: 280px)",
    "graficoWidth": "270px",
    "divClass": "div-test xs280"
  }'
  xs320='{
    "rule": "xs320 (min-width: 320px)",
    "graficoWidth": "310px",
    "divClass": "div-test xs320"
  }'
  xs360='{
    "rule": "xs360 (min-width: 360px)",
    "graficoWidth": "350px",
    "divClass": "div-test xs360"
  }'
  xs375='{
    "rule": "xs375 (min-width: 375px)",
    "graficoWidth": "365px",
    "divClass": "div-test xs375"
  }'
  xs390='{
    "rule": "xs390 (min-width: 390px)",
    "graficoWidth": "280px",
    "divClass": "div-test xs390"
  }'
  xs412='{
    "rule": "xs412 (min-width: 412px)",
    "graficoWidth": "312px",
    "divClass": "div-test xs412"
  }'
  xs540='{
    "rule": "xs540 (min-width: 540px)",
    "graficoWidth": "530px",
    "divClass": "div-test xs540"
  }'
  sm='{
    "rule": "sm (min-width: 576px)",
    "graficoWidth": "476px",
    "divClass": "div-test sm"
  }'
  md='{
    "rule": "md (min-width: 768px)",
    "graficoWidth": "668px",
    "divClass": "div-test md"
  }'
  md912='{
    "rule": "md912 (min-width: 912px)",
    "graficoWidth": "812px",
    "divClass": "div-test md912"
  }'
  md820='{
    "rule": "md820 (min-width: 820px)",
    "graficoWidth": "720px",
    "divClass": "div-test md820"
  }'
  lg='{
    "rule": "lg (min-width: 992px)",
    "graficoWidth": "892px",
    "divClass": "div-test lg"
  }'
  lg1024='{
    "rule": "lg1024 (min-width: 1024px)",
    "graficoWidth": "924px",
    "divClass": "div-test lg1024"
  }'
  xl='{
    "rule": "xl (min-width: 1200px)",
    "graficoWidth": "1100px",
    "divClass": "div-test xl"
  }'
  xl1280='{
    "rule": "xl1280 (min-width: 1280px)",
    "graficoWidth": "1180px",
    "divClass": "div-test xl1280"
  }'
  xxl='{
    "rule": "xxl (min-width: 1400px)",
    "graficoWidth": "1300px",
    "divClass": "div-test xxl"
  }'
  default="xxl"
  context="mediaquery"
  storage="true"
>
</media_query>
<span context="mediaquery" var-children="rule"></span>
<div context="mediaquery" var-class="divClass"></div>
*/
export const MediaQuery = ({children, context, storage, debug = "false", ...items}) => {

  const dispatch = useDispatch();
  const [refresh, setRefresh] = useState(0);
  const [matches, setMatches] = useState([]);
  
  const defaultData = useMemo(()=> items.default && items[items.default] && processOptions(items[items.default], null, null), [items.default]);
  const itemsStr = JSON.stringify(items);

  useEffect( () => {
    const filterMatchQueries = matchQueries.filter(x => items[x.name]);
    const newMatches = filterMatchQueries.reduce((prev, curr) => ({
      ...prev,
      [curr.name]: processOptions(items[curr.name], null, null)
    }), {});
    setMatches(newMatches);
    filterMatchQueries.forEach(x=>{
      x.matchMedia.addEventListener('change', handleChange);
    });
    return ()=>{      
      filterMatchQueries.forEach(x=>{
        x.matchMedia.removeEventListener('change', handleChange);
      });
    }
  }, [itemsStr]);

  const handleChange = (event) => {
    if(debug==="true"){
      console.log(event);
    }
    setRefresh(prev => prev + 1);
  }

  const mediaMatch = useMemo( () => {
    if(!matches) return null;
    const firstMatch = matchQueries.find(x => x.matchMedia.matches && matches[x.name]);
    if(debug==="true"){
      console.log( matchQueries.map(x => ({...x, match: x.matchMedia.matches})))
    }
    if(firstMatch){
      return matches[firstMatch.name];
    }else{
      return defaultData;
    }
  }, [refresh, matches]);

  useEffect(()=>{
    dispatch(appActions.setVariable("", mediaMatch, context, storage))
  }, [mediaMatch, context, storage]);

  return (
    <>{children}</>
  )
}