Dynamic media query with React Hooks

Tags: , ,



i’m trying to build a Bootstrap Grid(with Container, Row, col-md-12, etc) from ground on React. My first problem is with Container. In Bootstrap, Container has multiple media queries. I could(i’m using Styled Components with Styled Tools) obviously create multiple media queries that will overwrite each other but i know this is not the best way.

I was thinking on creating a way to check user window.innerWidth and change it with useEffect but i’m having some problems on developing my own logic and also if this is going to hurt performance. This is what i have so far.

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { ifProp, ifNotProp } from "styled-tools";

const mediaQuerie = (minWidth, maxWidth) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
  }`;
};

const StyledContainer = styled.div`
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(576, 540)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(768, 720)}
        ${mediaQuerie(992, 960)}
        ${mediaQuerie(1200, 1140)}
      `
  )}
`;

const Container = props => {
  const [width, setWidth] = useState();

  setWidth(window.innerWidth);
  useEffect(() => {
    console.log(window.addEventListener("resize", width));
  }, [width]);

  return <StyledContainer {...props} />;
};

Container.propTypes = {
  children: PropTypes.node,
  fluid: PropTypes.bool
};
Container.defaultProps = {
  xs: 0,
  sm: 576,
  md: 768,
  lg: 992,
  xl: 1200
};

export default Container;

Answer

In my opinion it’s always better to use multiple media queries to target different screen sizes. Using something available out of the box would have performance gains than a custom built logic.

If you still want to go the other way around, ie Apply dynamic styles based on screen size using resize listener. You could tweak your code as follows.

Make sure event listeners are added only once. For this your useEffect should change to

useEffect(() => {
    setWidth(window.innerWidth);
    window.addEventListener("resize", () => setWidth(window.innerWidth));
  }, []);
  • The first line setWidth(window.innerWidth); sets the width on initial render, because at that moment the resize event is never triggered.
  • The second line window.addEventListener("resize", () => setWidth(window.innerWidth)); registers the listener to update the state (width)
  • The empty array [] passed to useEffect makes sure the event listener is added only once

Now create a function that accepts the width and returns the style props. For example I have created a function fluidMediaQuery that adds a background color to the styled container.

const mediaQuerie = (minWidth, maxWidth, color) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
    background-color: ${color}
  }`;
};

const fluidMediaQuery = width => {
  if (width > 1200) {
    return mediaQuerie(1140, 1200, "green");
  } else if (width > 992) {
    return mediaQuerie(992, 960, "blue");
  } else if (width > 768) {
    return mediaQuerie(720, 768, "red");
  } else {
    return mediaQuerie(540, 576, "yellow");
  }
};

Your styled component can now take a fluid prop and render differently.

const StyledContainer = styled.div`
  width: 100%;
  height: 100px;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(540, 576)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(720, 768)}
        ${mediaQuerie(960, 992)}
        ${mediaQuerie(1140, 1200)}
      `
  )}
  ${ifProp(
    "fluid",
    css`
      ${props => (props.width ? fluidMediaQuery(props.width) : "")}
    `
  )}
`;

And finally render the component <StyledContainer fluid={true} width={width} /> Here width is coming from the state, which is set by resize event listener.

Completed code might look like below. Try resizing the screen and you should be able to see the background color change.

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { ifProp, ifNotProp } from "styled-tools";

const mediaQuerie = (minWidth, maxWidth, color) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
    background-color: ${color}
  }`;
};

const fluidMediaQuery = width => {
  if (width > 1200) {
    return mediaQuerie(1140, 1200, "green");
  } else if (width > 992) {
    return mediaQuerie(992, 960, "blue");
  } else if (width > 768) {
    return mediaQuerie(720, 768, "red");
  } else {
    return mediaQuerie(540, 576, "yellow");
  }
};

const StyledContainer = styled.div`
  width: 100%;
  height: 100px;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(540, 576)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(720, 768)}
        ${mediaQuerie(960, 992)}
        ${mediaQuerie(1140, 1200)}
      `
  )}
  ${ifProp(
    "fluid",
    css`
      ${props => (props.width ? fluidMediaQuery(props.width) : "")}
    `
  )}
`;

const Container = props => {
  const [width, setWidth] = useState();
  useEffect(() => {
    setWidth(window.innerWidth);
    window.addEventListener("resize", () => setWidth(window.innerWidth));
  }, []);

  return (
    <div>
      <h4>Width of screen: {width}</h4>
      <StyledContainer fluid={true} width={width} />
    </div>
  );
};

Container.propTypes = {
  children: PropTypes.node,
  fluid: PropTypes.bool
};
Container.defaultProps = {
  xs: 0,
  sm: 576,
  md: 768,
  lg: 992,
  xl: 1200
};

export default Container;

Important note. I’m not sure if resize is triggered in all cases. For example in case of orientation change on a mobile device, would this be triggered or not?. You might need to do some research on those aspects. The below post might help.

Will $(window).resize() fire on orientation change?



Source: stackoverflow