Cross-Platform Width in Context
I have been working with React Native Web (RNW) and came across a need for Media Query-like behavior within components. While working on cross-platform components, sometimes a component needs to know what the dimensions of the screen are. Luckily, RNW comes with handy Dimensions API and React now has a Context API. With these two combined, the sky is the limit!
Code
To get started, create a Context component. I did it like this:
import React from 'react'; export default React.createContext({ width: 0, height: 0, });
Next, we will need to modify our highest-up component...for my code's structure, that is an
App.js
file, which sits above all other files. Here is a rundown of the changes:- create state which will store the current height and width of the browser
- create a listener that will react to changes in the browser dimension
- wrap the Provider (loaded it up with the width and height) around child components
import React from 'react'; import { throttle, get } from 'lodash'; import { Dimensions } from 'react-native-web'; import DimensionsContext from '../DimensionsContext'; const throttledOnDimensionsChange = throttle(callback => callback(), 1000, { trailing: true, }); class App extends React.PureComponent { state = { height: Dimensions.get('window').height, width: Dimensions.get('window').width, }; componentDidMount() { Dimensions.addEventListener('change', () => this.onDimensionsChange()); } componentWillUnmount() { Dimensions.removeEventListener('change', () => this.onDimensionsChange()); } onDimensionsChange() { throttledOnDimensionsChange(() => { const { height, width } = Dimensions.get('window'); this.setState({ height, width }); }); } render() { ... const { height, width } = this.state; return ( <DimensionsContext.Provider value={{ height, width }}> ... </DimensionsContext.Provider> ); } }
A couple notes from the code above:
- Lodash is used to throttle the dimensions changes...this ensures we are not re-rendering too quickly when a user resizes their browser
- Lodash is also used to get the width and height...this is probably not required, but it provides a little safety
With the top component all set, the next step is to start using the Consumer. This is easy work within a component:
... import DimensionsContext from '../DimensionsContext'; const SomeComponent = () => ( <DimensionsContext.Consumer> {({ width }) => ( <View style={width < 800 ? styles.containerMobile : styles.container}> ... </View> )} </DimensionsContext.Consumer> );
By wrapping the component in
DimensionsContext.Consumer
the component now has access to a dynamic width, which will cause the component to re-render when the width changes. This is very helpful, because now the width property can be used to toggle styles on and off and do other Media Query-like actions.Notes
- It's worth mentioning that wrapping a lot of components in
DimensionsContext.Consumer
could be a slow-down—I do not know and I have not tested it thoroughly. From the limited experience I have with it, the UI remained quick and it was not an issue. - If you are not using RNW, this technique and definitely be implemented a bit more manually, but this solution should be cross-platform friendly.
Posted October 15th, 2018.