A custom media query implementation with Redux

5 September 2020

How to realize responsive styling in a cross-platform app.

When working with styling in JavaScript, we need to adapt our habits to writing styles. We might have to find custom ways to handle things we are used to from CSS.

I learned that recently, when working on a project that combines a web app, an Android app and an iOS app in one codebase. Since React Native doesn't have any responsive styling capabilities out of the box, we built a custom approach using the state management system Redux to get something similar to media queries, that works for all platforms in React Native for Web.

In this article I am going to explain how we did that.

If you want to learn more about the project in general, check out my overview article.

Table of contents

  1. Dispatching an action
  2. Setting up the reducer
  3. Merging the styles
  4. Writing and assigning stylesheets
  5. Summary

Dispatching an action

First we define a Redux state for the current breakpoint. It will be set on each layout change of the outermost component.

<View onLayout={(event) => {
  store.dispatch({
    type: 'SET_BREAKPOINT',
    width: event.nativeEvent.layout.width
  })
}}>
  // ...
</View>

The above code dispatches an action to set the breakpoint according to the current screen width, which we get from the onLayout event.

Setting up the reducer

Here is the reducer that actually sets the current breakpoint:

const breakpoints = {
  XL: 1200,
  LG: 960,
  MD: 720,
  XS: 540
};

const initialState = 'XS';

const breakpoint = (state = initialState, action) => {
  switch(action.type) {
    case 'SET_BREAKPOINT':
      let breakpoint = Object.keys(breakpoints).find((key) => action.width >= breakpoints[key]);
      return breakpoint || initialState;
    default:
      return state;
  }
}

First we define our breakpoints in an object. Then inside the reducer, the width is taken from the action and the matching key from the breakpoints object is returned as the new state.

Merging the styles

We can now write a little helper function that takes a StyleSheet object, merge all styles up until the current breakpoint and returns them in the right order. As we work with a mobile first approach, the base styles are the ones for the smallest screens. From there all the larger sizes are merged in order until the current breakpoint, so that larger breakpoints overwrite smaller ones.

const getResponsiveStyle = (styleSheet) => {

  const breakpoint = store.getState().breakpoint;

  let style = [];

  switch(breakpoint) {
    case 'XL':
      style.push(styleSheet.XL);
    case 'LG':
      style.push(styleSheet.LG);
    case 'MD':
      style.push(styleSheet.MD);
    case 'XS':
      style.push(styleSheet.XS);
    default:
      style.push(styleSheet.base);
  }

  return style.reverse();
}

Writing and assigning stylesheets

And that's it. We are now able to easily define styles for different breakpoints and assign them an the element using the helper funtion. For example:

const styleSheet = StyleSheet.create({
  base: {
    margin: 16
  },
  MD: {
    margin: 24
  },
  LG: {
    margin: 48
  }
});

return (
  <View style={getResponsiveStyle(styleSheet)}></View>
)

Summary

That is just one approach to defining different styles per breakpoint in React Native for Web. If you have other suggestions or questions, please do not hesitate to contact me.


I have written more about React Native for Web:

More blog posts