React Native platform OS rendering with Typescript
12 jul 2022Table of contents
- Table of contents
- Introduction
- The map example
- Platform OS specific component
- Ending
- Working example
Introduction
Expo is a great tool. Allowing anyone to create a react-native mobile and web app. But comes to a point where a lib you need is not supported by default on every OS you want to build on. Here is the list of OS where an expo app can be run:
/**
* @see https://reactnative.dev/docs/platform-specific-code#content
*/
export type PlatformOSType =
| "ios"
| "android"
| "macos"
| "windows"
| "web"
| "native";
A solution would be to render specific react-native component using if
or ternaries operator. It’s not recommended as it brings a double lecture to the code and is not idiomatic to expo.
Expo provides a Platform
module, which contains a select method that allow you to return anything depending on the platform OS.
Platform dynamic style declaration example:
import { Platform, StyleSheet } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
...Platform.select({
android: {
backgroundColor: "green",
},
ios: {
backgroundColor: "red",
},
default: {
// other platforms, web for example
backgroundColor: "blue",
},
}),
},
});
The map example
Here we want to display a map on both mobile
and web
, using expo MapView on mobile and react-native-maps on web.
As we could see on the above example Platform.select
have infinite use cases.
For our map issue we wanna dynamically render react component, but also sharing the same typed props between each of them, let’s say the default map coords.
Dynamic component rendering example:
const Component = Platform.select({
ios: () => require("ComponentIOS"),
android: () => require("ComponentAndroid"),
})();
<Component />;
Platform OS specific component
Our component have to be sharing same typed props. Then we can establish the following types:
// contract.ts
export interface LatlngCoords {
lat: number;
lng: number;
}
export interface MapComponentProps {
defaultMapCenterCoords: LatlngCoords;
}
export type MapFunctionComponent = React.FC<MapComponentProps>;
We want to display this component on web:
PS: note that div are then available in a web context.
// web.tsx
import GoogleMapReact from "google-map-react";
import React from "react";
import { MapFunctionComponent } from "./contract";
const WebMaps: MapFunctionComponent = ({ defaultMapCenterCoords }, _) => {
return (
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{ key: "" }}
defaultCenter={defaultMapCenterCoords}
defaultZoom={9}
></GoogleMapReact>
</div>
);
};
export default WebMaps;
And we want to display this component on native:
// native.tsx
import React from "react";
import MapView from "react-native-maps";
import { MapFunctionComponent } from "./contract";
const NativeMaps: MapFunctionComponent = ({ defaultMapCenterCoords }) => {
return (
<MapView
initialRegion={{
latitude: defaultMapCenterCoords.lat,
longitude: defaultMapCenterCoords.lng,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
style={{
width: "100%",
height: "100%",
}}
provider={"google"}
/>
);
};
export default NativeMaps;
Now that we have both what we want to render on web and native, the last thing to do is to compute everything inside a Platform.select
call and export the resulting component.
// index.tsx
import { Platform } from "react-native";
import { MapFunctionComponent } from "./contract";
const MapComponent: MapFunctionComponent = Platform.select({
native: () => await import("./native").default,
default: () => await import("./web").default,
})();
export default MapComponent;
export * from "./contract";
Finally, use the mapComponent
wherever you want.
// App.tsx
import * as React from "react";
import { SafeAreaView } from "react-native";
import MapComponent from "./MapComponent";
export default function App() {
const defaultMapCenterCoords = {
lat: 48.866667,
lng: 2.333333,
};
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
}}
>
<MapComponent defaultMapCenterCoords={defaultMapCenterCoords} />
</SafeAreaView>
);
}
Ending
During discovering expo we could easily think that ejecting would be a required end at some point.
But using expo is really smooth, it provides a lot of tools to answer specific needs idiomatically.
Combining advanced react-native
and platform.select
usage, such as ExoticComponent
to type safely forward refs
can lead to powerful and readable architecture.
Working example
You can find a working example repository below expo-platform-select-example →
Thanks for reading. Any suggestions are welcomed !