useMedia React sensor hook that tracks state of a CSS media query.
You can installuse-media with npm
npm install --save use-media
or with yarn
WithuseEffect
importuseMediafrom'use-media';// Alternatively, you can import as:// import {useMedia} from 'use-media';constDemo=()=>{// Accepts an object of features to testconstisWide=useMedia({minWidth:'1000px'});// Or a regular media query stringconstreduceMotion=useMedia('(prefers-reduced-motion: reduce)');return(<div> Screen is wide:{isWide ?'😃' :'😢'}</div>);};WithuseLayoutEffect
import{useMediaLayout}from'use-media';constDemo=()=>{// Accepts an object of features to testconstisWide=useMediaLayout({minWidth:'1000px'});// Or a regular media query stringconstreduceMotion=useMediaLayout('(prefers-reduced-motion: reduce)');return(<div> Screen is wide:{isWide ?'😃' :'😢'}</div>);};Depending on your testing setup, you may need to mockwindow.matchMedia on components that utilize theuseMedia hook. Below is an example of doing this injest:
/test-utilities/index.ts
import{mockMediaQueryList}from'use-media/lib/useMedia';// Types are also exported for convienence:// import {Effect, MediaQueryObject} from 'use-media/lib/types';exportinterfaceMockMatchMedia{ media:string;matches?:boolean;}functiongetMockImplementation({media, matches=false}:MockMatchMedia){constmql:MediaQueryList={ ...mockMediaQueryList, media, matches,};return()=>mql;}exportfunctionjestMockMatchMedia({media, matches=false}:MockMatchMedia){constmockedImplementation=getMockImplementation({media, matches});window.matchMedia=jest.fn().mockImplementation(mockedImplementation);}/components/MyComponent/MyComponent.test.tsx
constmediaQueries={mobile:'(max-width: 767px)',prefersReducedMotion:'(prefers-reduced-motion: reduce)',};describe('<MyComponent />',()=>{constdefaultProps:Props={duration:100,};afterEach(()=>{jestMockMatchMedia({media:mediaQueries.prefersReducedMotion,matches:false,});});it('sets `duration` to `0` when user-agent `prefers-reduced-motion`',()=>{jestMockMatchMedia({media:mediaQueries.prefersReducedMotion,matches:true,});constwrapper=mount(<MyComponent{...defaultProps}/>);constchild=wrapper.find(TransitionComponent);expect(child.prop('duration')).toBe(0);});});Depending on your app, you may be using theuseMedia hook to register manymatchMedia listeners across multiple components. It may help to elevate these listeners toContext.
/components/MediaQueryProvider/MediaQueryProvider.tsx
importReact,{createContext,useContext,useMemo}from'react';importuseMediafrom'use-media';interfaceProps{ children:React.ReactNode;}exportconstMediaQueryContext=createContext(null);constmediaQueries={mobile:'(max-width: 767px)',prefersReducedMotion:'(prefers-reduced-motion: reduce)',};exportdefaultfunctionMediaQueryProvider({children}:Props){constmobileView=useMedia(mediaQueries.mobile);constprefersReducedMotion=useMedia(mediaQueries.prefersReducedMotion);constvalue=useMemo(()=>({mobileView, prefersReducedMotion}),[mobileView,prefersReducedMotion,]);return(<MediaQueryContext.Providervalue={value}>{children}</MediaQueryContext.Provider>);}exportfunctionuseMediaQueryContext(){returnuseContext(MediaQueryContext);}/components/App/App.tsx
importReactfrom'react';importMediaQueryProviderfrom'../MediaQueryProvider';importMyComponentfrom'../MyComponent';exportdefaultfunctionApp(){return(<MediaQueryProvider><divid="MyApp"><MyComponent/></div></MediaQueryProvider>);}/components/MyComponent/MyComponent.tsx
importReactfrom'react';import{useMediaQueryContext}from'../MediaQueryProvider';exportdefaultfunctionMyComponent(){const{mobileView, prefersReducedMotion}=useMediaQueryContext();return(<div><p>mobileView:{Boolean(mobileView).toString()}</p><p>prefersReducedMotion:{Boolean(prefersReducedMotion).toString()}</p></div>);}