Building a React Native SDK
Just like with React, a React Native library could be anything, e.g. a component, multiple components that abstracts some repetitive code and makes it easier to use.
We're going to be focused on building a React Native library for the pay-script we had deployed. There're two ways to go build this:
- Building the component(s) from scratch and publishing the component(s) or;
- Injecting our pay-script into a WebView component in React Native and publishing it
We're doing the latter for this piece.
Prerequisite
- Understanding JavaScript basics, JavaScript functions, Switch statements
- Knowledge of React Native
Step 1
Open up your terminal and make a new directory called pay-app-rn
One simple way of creating a React Native library is by using create-react-native-library cli.
To create a new library, simply run:
npx create-react-native-library pay-app-rn
Some configuration options would follow after running the command in your terminal. For template, select typescript and for package manager, use npm. Every other thing can be altered from the package.json file.
After configuring your library, our module would be created. Run the following
cd pay-app-rn && npm install
Next let us install a package to build our code for multiple targets
npx react-native-builder-bob init
Afterwards, we will install our dependency
npm install react-native-webview
After installation, add it as a peerDependency in your package.json file. Adding dependencies to peerDependency ensures there would not be a conflict between your specified version and whatever version your library's users are running at the time of installation.
Step 2
Let's create our directories and components in our src folder. In your terminal, run the code below to make our directories:
cd src && mkdir components types utils
Now we will create the files we need in each of these directories. Run the code below to create these files
cd components && touch Error.tsx Loader.tsx && cd .. &&
cd utils && touch index.ts && cd .. &&
cd types && touch index.ts
Step 3
Open the Error.tsx and paste the code below:
// Error.tsx
import React from "react";
import { View, StyleSheet, Text, Platform, Pressable } from "react-native";
const ErrorFallback = ({ onClose }: { onClose: () => void }) => {
return (
<View style={styles.container}>
<Pressable
android_ripple={{ color: "rgba(0,0,0,0.15)" }}
style={({ pressed }: { pressed: boolean }) => [
{
opacity: Platform.OS === "ios" && pressed ? 0.2 : 1,
padding: 20,
marginRight: -20,
position: "absolute",
alignItems: "flex-end",
alignSelf: "flex-end",
},
]}
onPress={onClose}
>
Close
</Pressable>
<Text style={styles.text}>Something went wrong. Try again</Text>
</View>
);
};
const styles = StyleSheet.create({
text: {
fontSize: 16,
color: "#727EA3",
fontWeight: "400",
marginTop: 10,
},
container: {
flex: 1,
backgroundColor: "#ffffff",
height: "100%",
width: "100%",
alignItems: "center",
position: "absolute",
},
});
export default ErrorFallback;
The component above is a fallback for when the WebView doesn't load up successfully, it gives the users a chance to close the SDK and reboot it
Open the Loader.tsx and paste the code below:
// Loader.tsx
import React from "react";
import { ActivityIndicator, View, StyleSheet } from "react-native";
const Loader = () => {
return (
<View style={styles.wrapper}>
<ActivityIndicator size="small" color="#0066FF" />
</View>
);
};
const styles = StyleSheet.create({
wrapper: {
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "white",
height: "100%",
},
});
export default Loader;
This is a simple loader we will show when the WebView is still loading.
Step 4
Open the index file and paste the code below:
// index.ts
import React from "react";
import { View, Modal, StyleSheet, Platform } from "react-native";
import type { ConfigProps } from "../types";
import { createUrl } from "../utils";
const Pay = (props: ConfigProps) => {
const { onClose, onSuccess, onError, openSDK } = props;
const handleMessage = ({ nativeEvent: { data } }: any) => {
const response = JSON.parse(data);
switch (response.event) {
// again, using the events we used at Thepeer, you should have cases that match your own emitted events
// case "pay.close":
case "send.close":
onClose();
break;
// case "pay.success":
case "send.success":
onSuccess(response);
break;
default:
onError(response);
break;
}
};
const sourceUrl = createUrl(props);
return (
<Modal
animationType="slide"
transparent={true}
visible={openSDK}
onRequestClose={onClose}
>
<View style={styles.container}>
<View style={styles.mainView}>
<WebView
source={{ uri: sourceUrl }}
onMessage={handleMessage}
startInLoadingState={true}
renderLoading={() => <Loader />}
renderError={(error) => <ErrorFallback {...{ onClose, error }} />}
/>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.6)",
paddingTop: Platform.OS === "ios" ? 48 : 36,
position: "relative",
},
mainView: {
position: "relative",
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
backgroundColor: "#ffffff",
overflow: "hidden",
height: "100%",
},
});
export default Pay;
This function basically exposes a modal that is housing our WebView. We generate a url with our config and pass it in as the source. The component would automatically load up our SDK.
handleMessage
This is a function to listen and react to the events emitted from our app (pay-app-react).
Step 5
You're probably getting errors in the code because of missing imports. Let's fix those 🏃🏻 Now we need to add our types to the type file.
1. Open types/index.ts and add the following types
// types/index.ts
type EventResponse = {
type: string;
data: undefined | Object;
};
export interface ConfigProps {
openSDK: boolean;
publicKey: string;
amount: string | number;
currency: string;
onSuccess: (response: EventResponse) => void;
onError: (response: EventResponse) => void;
onClose: (response?: EventResponse) => void;
}
2. Open utils/index.ts and add the following types
// utils/index.ts
const isRequired = (prop: string, isValid: boolean) => {
if (isValid) return;
throw new Error(`${prop} is required`);
};
const validateAmount = ({
amount,
currency,
}: {
amount: string | number;
currency: string;
}) => {
if (!amount) return isRequired("amount", false);
const amt = +amount;
if (!isNaN(amt) && currency === "NGN" && amt < 10000) {
throw new Error("amount cannot be less than ₦100");
} else {
throw new Error("amount must be a number");
}
};
const validateConfig = (config: any) => {
const { amount, publicKey, onClose, onSuccess, onError, currency } = config;
isRequired("publicKey", !!publicKey);
isRequired("currency", !!currency);
isRequired("onClose callback", onClose !== undefined);
isRequired("onError callback", onError !== undefined);
isRequired("onSuccess callback", onSuccess !== undefined);
validateAmount({ amount, currency });
return true;
};
const createUrl = (config: any) => {
const configValid = validateConfig(config);
let base = "https://chain.thepeer.co?";
if (!configValid) return base;
Object.keys(config).map((k) => {
if (config[k]) {
const val = k === "meta" ? JSON.stringify(config[k]) : config[k];
base = base.concat(`${k}=${val}&`);
}
});
return base.slice(0, -1);
};
export { createUrl };
1. createUrl
This function appends our props as a query to our pay-app-react's url. It calls our validateConfig to validate our configs first and returns a url depending on the validation result.
2. validateAmount
Validate the amount and currency passed and errors out if there's a problem.
Step 6: Testing locally
We're pretty much done with the SDK. Now we can bundle it and test it out. To bundle and create an installable file, run:
npm run prepare && npm pack
You should see a pay-app-rn-v0.1.0.tgz in the root of your project
You can install it in your example folder using the absolute path to the file.
To publish to NPM
- Create an account if you don't already have one
- After creating the account, open your terminal and run
npm login
- After logging in, run
npm run prepare && npm publish
- Fill out the questions asked and viola!