DEV Community

Discussion on: How to Do a Device Based Code Split in React

Collapse
 
link2twenty profile image
Andrew Bone

I like to have a custom hook like this in my projects. It in theory works back to IE10.

export default function useMatchMedia(query) {
  const [state, setState] = useState(null);

  useEffect(() => {
    const media = window.matchMedia(query);
    const func = (e) => {
      setState(e.matches);
    }

    try {
      media.addEventListener('change', func);
    } catch {
      media.addListener(func);
    }

    return () => {
      try {
        media.removeEventListener('change', func);
      } catch {
        media.removeListener(func);
      }
    }

  }, []);

  return state;
}
Enter fullscreen mode Exit fullscreen mode

It means you can do things like this to track media query changes.

const isMobile = useMatchMedia("(pointer: coarse) and (hover: none)");

useEffect(()=>{
  console.log(`somehow is mobile changed it is now ${isMobile}`)
}, [isMobile]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
moubi profile image
Miroslav Nikolov

Andrew that looks ok and will also work in the last project I did.

I had to deal with different approaches for getting the touch/desktop detection right:

  • server side detection
  • using the browser string in the frontend
  • using media queries

Things may become very annoying, that's why I decided to back my self up.

The detection logic is central for the app so you can extract it to the store. I am personally using Redux.

To supplement what has already been said in the article here is an implementation for those using some kind of store (redux in here):

Reducer + selector

// ui.js reducer
import { qualifySelector } from "../utils";
import { mobileQuery } from "../../lib/mediaQueries";

const name = "ui";
const initialState = {
  isTouch: mobileQuery.matches
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "SET_TOUCH_SUCCESS":
      return {
        isTouch: action.payload
      };
    default:
      return state;
  }
}

export default { [name]: reducer };

export const isMobileView = qualifySelector(name, state => state.isTouch);
Enter fullscreen mode Exit fullscreen mode

And the<Import /> component.

// Import.js
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { isMobileView } from "../../../store/selectors";

export function Import({ touch, desktop, isMobile, children }) {
  const [Component, setComponent] = useState(null);

  useEffect(() => {
    const importCallback = isMobile ? touch : desktop;

    importCallback().then(componentDetails => {
      setComponent(componentDetails);
    });
  }, [desktop, touch, isMobile]);

  return children(Component ? Component.default : () => null);
}

Import.propTypes = {
  touch: PropTypes.func,
  desktop: PropTypes.func,
  children: PropTypes.func.isRequired,
  isMobile: PropTypes.bool.isRequired
};

export default connect(state => ({
  isMobile: isMobileView(state)
}))(Import);
Enter fullscreen mode Exit fullscreen mode

That way you will have detection logic agnostic approach and a central place to store the truth.

It may look like you are introducing a lot of unnecessary code, but it will pay off if your device detection changes dynamically during app lifecycle.


Fx. switching portrait to landscape mode may lead to serving desktop version instead of the touch one. Therefore you will need to update the store realtime with an action.