Search code examples
reactjsnext.jsmedia-queriesstyled-components

how to use window.matchMedia in next.js with style components theme?


I have a theme:

const smartPhonePortrait = 320;


const theme = {
  isCompact:
    typeof window !== 'undefined'
      ? window.matchMedia(`(min-width:${smartPhonePortrait}px)`).matches
      : false
};

export default theme;

And then I add this theme to my provider

inside of _app.js:

import { ThemeProvider } from 'styled-components';
import theme from '../theme';

const App = ({ Component, pageProps, router }) => {
  return (
    <ThemeProvider theme={theme}>
     ... my app
    </ThemeProvider>
  );
};

and in my styled component I do this:

const Item = styled.div`
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: ${(props) => (props.theme.isCompact ? '100%' : '48%')};
`;

This works on first render but when I change screen size it does not update. If im in mobile I get flex-basis 100% but if I change the screen size to desktop I dont get 48%

How do I make it work?


Solution

  • What you could do is make a hook:

    import { useState, useCallback, useEffect } from 'react';
    const useMediaQuery = (width) => {
      const [targetReached, setTargetReached] = useState(false);
    
      const updateTarget = useCallback((e) => {
        if (e.matches) {
          setTargetReached(true);
        } else {
          setTargetReached(false);
        }
      }, [setTargetReached]);
    
      useEffect(() => {
        if (typeof window !== 'undefined') {
          const media = window.matchMedia(`(max-width: ${width}px)`);
          media.addEventListener('change', updateTarget);
    
          if (media.matches) {
            setTargetReached(true);
          }
    
          return () => media.removeEventListener('change', updateTarget);
        }
      }, []);
    
      return targetReached;
    };
    
    export default useMediaQuery;
    

    and in your __app.js you import it and add it to your theme:

    import MediaQuery from 'hooks/useMedia';
    
    const isCompact = MediaQuery(576);
    const useTheme = { ...theme, isCompact };
    
    <ThemeProvider theme={useTheme}>
      ... my app
    </ThemeProvider>