Друкарня від WE.UA

React-хук для роботи з matchMedia

Готовий кастомний хук. Достатньо скопіювати — і матимете зручну можливість отримувати дані від matchMedia, задавати кастомні медіа-запити, легко їх змінювати та використовувати все це у React.

У попередній статті про різноманітні підходи до роботи над адаптивністю веб-сайтів я згадувала matchMedia — JavaScript API, який дозволяє отримувати відповіді true/false на будь-який ваш медіа-запит та керувати кодом в залежності від розміру екрану. Це напрочуд крутий інструмент в разі якщо у вашому проекті багато елементів, що мають реагувати на зміни екрану у корстувача. Проте його не можна використовувати за просто так у React. Як спеціаістка, що найбільше працює саме з рекатом, я написала кастомний хук, яким ділюся з вами.

Повний код хука на CodePen

А тепер по порядку

В статті я розказую про:

  1. npm-пакет для роботи з matchMedia, який вже існує

  2. плюси свого хука

  3. що відбувається під капотом


На npm можна знайти пакет react-use-match-media для роботи з matchMedia в React і він більш ніж корисний, якщо у вас в проекті необхідність звертатись до медіа-запитів саме через js сягає 1-3 разів на весь код. Тоді не треба винаходити велосипеда, просто беріть цей пакет. Приблизно так його використовують:

const Example = (props) => {
  const isWideViewport = useMatchMedia('(min-width: 600px)');

  return <div>{isWideViewport ? 'Wide' : 'Narrow'}</div>;
};

Незручніть полягає в тому, що хук стає важким у застосуванні в разі, коли ваш проект потребує багато різних медіа-запитів в різних місцях коду. По-перше вам необхідно кожного разу вручну прописуати конкретний запит — як у прикладі '(min-width: 600px)'. В разі, якщо параметри на всьому проекті зміняться — доведеться шукати по проекту усі випадки його застосування, або знову ж таки зазделегідь обгортати цей пакет в додатковий код. До того ж все це виглядає не сказати, що інтуітивно зрозуміло, тому нові люди на проекті витратять більше часу, щоб розібратись з адаптивністю.

Що натомість пропоную я:

В кастомний хук ви передаєте не рядок, а свій об’єкт, куди прописуєте одразу декілька медіа-запитів з інтуітино зрозумілими назвами, наприклад ось такий:

const myMediaQueries = {
    mobile: '(max-width: 480px)',
    tablet: '(min-width: 481px) and (max-width: 767px)',
    desktop: '(min-width: 768px)',
}

І хук повертає вам також готовий об`єкт з відповідними ключами та значеннями true/false, який можна одразу зручно розпарсити:

// [object Object] 
{
  "mobile": true,
  "tablet": false,
  "desktop": false
}
export const Header = () => {

  const { mobile, tablet, desktop } = reactMatchMedia(myMediaQueries);

  return mobile ? <div>Hello, Mobile World<div> : <div>Hello, World</div>

};

Ба більше (!), якщо вам знадобиться прибрати чи додати один з параметрів, достатньо це зробити в початковій об’єкті-константі myMediaQueries. На прикладі нижче ми додаємо в наш проект великі екрани:

const myMediaQueries = {
    mobile: '(max-width: 480px)',
    tablet: '(min-width: 481px) and (max-width: 767px)',
    desktop: '(min-width: 768px) and (max-width: 1023px)',
    bigScreen: '(min-width: 1024px)',
}

Хук тепер повертає також і новий ключ, про що можна переконатись у консолі:

// [object Object] 
{
  "mobile": false,
  "tablet": false,
  "desktop": false,
  "bigScreen": true
}

Також без вагань ви можете додати в свій об’єкт запити не тільки розімірів, а й орієнтацій, наприклад:

const myMediaQueries = {
    mobile: '(max-width: 480px)',
    tablet: '(min-width: 481px) and (max-width: 767px)',
    desktop: '(min-width: 768px) and (max-width: 1023px)',
    landscape: '(orientation: landscape)',
}

Тепер, розпарсивши результат, легше проводити більш комплексні логічні операції:

export const Header = () => {

  const { mobile, landscape } = reactMatchMedia(myMediaQueries);
  if (mobile && landscape) {
    return (
      <div> 
        *для горизонтальної орієнтації*
      <div>
)};

Таким чином, завдяки цьому хуку, можна брати потрібні значення, які мають зрозумілі назви, парсити їх на початку компонента і далі з легкістю вживати де завгодно.

Під капотом

// Хук
const reactMatchMedia = (mediaQueries) => {
  
  const queries = Object.values(mediaQueries).map((query) => matchMedia(query));

  const [values, setValues] = useState(() => {
    const valuesObject = Object.keys(mediaQueries).reduce((acc, key, index) => {
      acc[key] = queries[index].matches;
      return acc;
    }, {});

    // Милиця! Для браузрених багів
    if (Object.values(valuesObject).every((val) => val === false)) {
      valuesObject.mobile = true;
    }

    return valuesObject;
  });

  useLayoutEffect(() => {
    const updateValues = () => {
      const valuesObject = Object.keys(mediaQueries).reduce(
        (acc, key, index) => {
          acc[key] = queries[index].matches;
          return acc;
        },
        {}
      );

      // Милиця! Для браузрених багів
      if (Object.values(valuesObject).every((val) => val === false)) {
        valuesObject.mobile = true;
      }

      setValues(valuesObject);
    };

    queries.forEach((query) => query.addEventListener("change", updateValues));
    return () =>
      queries.forEach((query) =>
        query.removeEventListener("change", updateValues)
      );
  }, [queries]);

  return values;
};

Власне, що тут відбувається?

Хук отримує кастомний об’єкт, використовує редуктор для отримання від matchMedia значень .matches для кожного ключа, щоб записати їх у початковий стан. Я використовую власне useState для створення стану та useLayoutEffect — для реагування на зміни.

Чому useLayoutEffect, а не звичайний useEffect? Звичайний useEffect виконується після того, як браузер оновить екран, іншими словами, після рендерингу компонентів. Це може спричинити миготіння або затримку в зміні стану компонентів, якщо значення медіа-запитів впливають на їх розміщення або вигляд.

Використання useLayoutEffect гарантує, що оновлення значень медіа-запитів буде відбуватись безпосередньо перед малюванням на екрані, що нам власне і потрібно.

У хуці є милиця. Вона створена для браузенрних багів при роботі з matchMedia. Це рідкість, але нажаль трапляється, або може трапитись в майбутньому після оновлень браузрерів. Деякі браузери в деяких випадках повертають false для всіх медіа-запитів, навіть якщо насправді одне зі значень має бути true. Для цього я в милиці встановлюю значення mobile як true примусово, щоб не зламати подальший код. Дефолтне значення може бути встановлено будь яке — най це буде desktop, якщо ваші користувачі ймовірніше заходять зі стаціонарного пристрою.

Цей хук не є легеньким, хоча б тому, що useLayoutEffect синхроний. Проте npm-пакет react-use-match-media так само під капотом використовує саме його. Мій кастомний хук важчий за react-use-match-media хіба через використання ще й редуктора, але він і приймає більш комплексні дані, а не один єдиний рядок.


Зберігайте собі, одного дня може згадаєте мене добрим словом! А я постараюсь писати тут частіше, хоча це не дуже й просто влітку, коли собаки просять гуляти з ними, а не сидіти за компом:)

Статті про вітчизняний бізнес та цікавих людей:

  • Бронеплівка для вікон: коли вона доречна та як обрати рішення

    Як бронеплівка утримує уламки, де її встановлюють та чому перед монтажем важливо оцінити стан і конструкцію скління.

    Теми цього довгочиту:

    Бронеплівка На Вікна
  • Шлейф сучасного смартфону та його призначення

    Шлейф - це тонка пластикова стрічка з ледь видимими доріжками і саме вона зʼєднує важливі компоненти мобільного, без яких він не запрацює. Власники пристроїв Xiaomi, які стикаються з потребою заміни цієї деталі, можуть підібрати відповідний варіант на сайті AKS.UA

    Теми цього довгочиту:

    Шлейф Для Мобільного
  • Чохли для iPhone 15: повний гід по кольорах, матеріалах і виробниках

    Перед тим як вибрати чохли для iPhone 15, варто визначитися, що саме ви хочете отримати від аксесуара. Комусь потрібен тонкий прозорий корпус, інший покупець шукає посилений захист, а для когось вирішальним стане колір або підтримка MagSafe

    Теми цього довгочиту:

    Чохли Для Iphone
  • Як побудована програма Meest China Academy

    Курс про товарний бізнес охоплює різні етапи роботи: від пошуку ідеї до перевірки товару, логістики та масштабування. Програма Meest China Academy містить 17 модулів, які послідовно розкривають ці теми без зведення всього навчання до однієї універсальної поради.

    Теми цього довгочиту:

    Meest
  • Які технології використані в Айфон 17

    Айфон 17 поєднує OLED-дисплей із частотою до 120 Гц, чип A19, дві камери Fusion 48 Мп і швидке заряджання через USB-C. У COMFY можна купити Айфон 17 з накопичувачем на 256 або 512 ГБ, вибравши конфігурацію відповідно до обсягу фото, відео та застосунків

    Теми цього довгочиту:

    Iphone 17
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Марта Ярчук
Марта Ярчук@yarchuk

3Довгочити
352Перегляди
39Підписники
На Друкарні з 18 квітня 2023

Більше від автора

Це також може зацікавити:

  • Які книги з JavaScript слід читати у 2024 році?

    Чому варто вивчати JavaScript у 2024 році? Які книги обрати для вивчення цієї мови програмування? У цій статті ми розглядаємо ключові аргументи на користь вивчення JavaScript у 2024 році та поділимося переліком ресурсів, які допоможуть вам у цьому.

    Теми цього довгочиту:

    Javascript
  • Дві проблеми двофакторної автентифікації ASP.NET Core Identity на основі TOTP

    В ASP.NET Core Identity, при використанні TOTP для 2FA, існують дві ключові проблеми: занадто довге часове вікно для верифікації кодів і відсутність перевірки на їх повторне використання, що підвищує ризик небезпеки.

    Теми цього довгочиту:

    Programming
  • Паттерн “Репозиторій“ в JS

    В попередній статті ми розібрались з паттерном Специфікація Сьогодні ми поговоримо про логічне продовження паттерн репозиторій.

    Теми цього довгочиту:

    Js

Коментарі (2)

Цікавий матеріал, але поправте назву хука, щоб по конвенціях була - починалася з use, будь ласка

Це також може зацікавити:

  • Які книги з JavaScript слід читати у 2024 році?

    Чому варто вивчати JavaScript у 2024 році? Які книги обрати для вивчення цієї мови програмування? У цій статті ми розглядаємо ключові аргументи на користь вивчення JavaScript у 2024 році та поділимося переліком ресурсів, які допоможуть вам у цьому.

    Теми цього довгочиту:

    Javascript
  • Дві проблеми двофакторної автентифікації ASP.NET Core Identity на основі TOTP

    В ASP.NET Core Identity, при використанні TOTP для 2FA, існують дві ключові проблеми: занадто довге часове вікно для верифікації кодів і відсутність перевірки на їх повторне використання, що підвищує ризик небезпеки.

    Теми цього довгочиту:

    Programming
  • Паттерн “Репозиторій“ в JS

    В попередній статті ми розібрались з паттерном Специфікація Сьогодні ми поговоримо про логічне продовження паттерн репозиторій.

    Теми цього довгочиту:

    Js