logo@krutoo/utils

useMatchMedia

Hook for state of media query matching.

import { useMatchMedia } from '@krutoo/utils/react';

function App() {
  const desktop = useMatchMedia('(min-width: 1024px)');

  return desktop ? <DesktopApp /> : <MobileApp />;
}

Stateless mode

If you want to observe media query without re-render your component when it changes, you can provide mode option with stateless value.

This will result in the return value not being up to date so you can ignore it.

To listen changes you also can set callback to onChange option.

Callback provided to onChange also will be called after initialization.

import { useMatchMedia } from '@krutoo/utils/react';

function App() {
  const [isDesktop, setIsDesktop] = useState(false);

  useMatchMedia('(min-width: 1024px)', {
    mode: 'stateless',
    onChange({ matches }) {
      setIsDesktop(matches);
    },
  });

  return desktop ? <DesktopApp /> : <MobileApp />;
}

MatchMediaContext

Under the hood this hook uses MatchMediaContext to create observer. MatchMediaContext by default uses global window.matchMedia.

But using global instance trough context gives you ability to replace implementation whenever you want.

It means that you can:

  • provide polyfill implementation for environments that don't support matchMedia;
  • provide implementation for SSR;
  • provide different implementations for different subtrees of your UI;
  • replace instance by mock in your unit tests.

For example next we will use MediaQueryListMock:

import { MatchMediaContext, useMatchMedia } from '@krutoo/utils/react';
import { MediaQueryListMock } from '@krutoo/utils/testing';
import { act, expect, render, test } from '#testing';

function MyWidget() {
  const mobile = useMatchMedia('(max-width: 1024px)');

  return <div className='my-widget'>{mobile ? 'Mobile widget' : 'Desktop widget'}</div>;
}

test('MyWidget should render message correctly', () => {
  let mql;

  const context = {
    matchMedia(query) {
      mql = new MediaQueryListMock(query);

      mql.simulateChange({ matches: true });

      return mql;
    },
  };

  const { container } = render(
    <MatchMediaContext value={context}>
      <MyWidget />
    </MatchMediaContext>,
  );

  expect(container.textContent).toBe('Mobile widget');

  act(() => {
    mql.simulateResize({ matches: false });
  });

  expect(container.textContent).toBe('Desktop widget');
});

This example shows that you don't need to use environment that implements matchMedia. So you can use jsdom for example.