DEV Community

hiro@
hiro@

Posted on • Originally published at qiita.com

Converting Invite QR Codes to PNG and Sharing in React Native

This article is an English translation of the original Japanese article.

I made it possible to display the app invite link as a QR code and share it directly as an image.

For displaying the QR code I use react-native-qrcode-svg, for converting the View to PNG I use react-native-view-shot, and for the share sheet I use expo-sharing.

npx expo install expo-sharing react-native-view-shot
pnpm add react-native-qrcode-svg react-native-svg
Enter fullscreen mode Exit fullscreen mode

Including the Organization Name in the Image, Not Just the QR Code

To ensure the content is clear from the shared image alone, I include both the QR code and the organization name in a single View.

const captureAreaRef = useRef<View>(null);

<View
  ref={captureAreaRef}
  collapsable={false}
  style={styles.captureArea}
>
  <Text style={styles.orgName}>{orgName}</Text>
  <QRCode
    value={inviteUrl}
    size={200}
    color={colors.foreground}
    backgroundColor={colors.card}
  />
</View>
Enter fullscreen mode Exit fullscreen mode

I add collapsable={false} to prevent React Native from omitting the native View through optimization, which would make the captureRef target unavailable.

I also specify the background color. If I share a transparent image and the recipient's background is dark, the QR code margins and organization name can become hard to read.

Saving the View as PNG

For the share button, I pass the View's ref to captureRef.

const captured = await captureRef(captureAreaRef, {
  format: "png",
  quality: 1,
});
Enter fullscreen mode Exit fullscreen mode

The returned value is the path to a temporary file. On Android, paths without a scheme can be returned, so I prepend file:// before sharing.

const uri = captured.startsWith("file://")
  ? captured
  : `file://${captured}`;
Enter fullscreen mode Exit fullscreen mode

Sharing with expo-sharing

I check if the sharing feature is available on the device before opening the share sheet.

if (await Sharing.isAvailableAsync()) {
  await Sharing.shareAsync(uri, {
    mimeType: "image/png",
    dialogTitle: `Invite QR code for ${orgName}`,
  });
} else {
  await Share.share({ url: uri });
}
Enter fullscreen mode Exit fullscreen mode

In the actual implementation, I also maintain state to prevent multiple share processes from starting on repeated taps.

const [sharing, setSharing] = useState(false);

const handleShareImage = async () => {
  if (sharing) return;
  setSharing(true);

  try {
    // captureRef and shareAsync
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    Alert.alert("Share failed", message);
  } finally {
    setSharing(false);
  }
};
Enter fullscreen mode Exit fullscreen mode

While processing, I disable the button and switch the label to "Preparing to share". Because there is a slight delay from capture to the share sheet opening, this prevents it from appearing unresponsive.

What to Put in the QR Code

The QR code contains an HTTPS invite URL that can also be opened on the web, rather than a custom scheme.

const inviteUrl = `${config.apiUrl}/invite/${orgId}`;
Enter fullscreen mode Exit fullscreen mode

On iOS, Universal Links are configured, so if the app is installed, the invite screen opens in the app. If not, the web version displays.

The QR image is not a screenshot. I limit the capture target to a dedicated View, so the modal close button and the "Share Image" button are not included in the image.

Full Implementation

Summarizing the key points, it looks like this:

export function InviteQrModal({ orgName, inviteUrl }: Props) {
  const captureAreaRef = useRef<View>(null);
  const [sharing, setSharing] = useState(false);

  const share = async () => {
    if (sharing) return;
    setSharing(true);
    try {
      const path = await captureRef(captureAreaRef, {
        format: "png",
        quality: 1,
      });
      const uri = path.startsWith("file://") ? path : `file://${path}`;
      await Sharing.shareAsync(uri, { mimeType: "image/png" });
    } finally {
      setSharing(false);
    }
  };

  return (
    <>
      <View ref={captureAreaRef} collapsable={false}>
        <Text>{orgName}</Text>
        <QRCode value={inviteUrl} size={200} />
      </View>
      <Pressable onPress={share} disabled={sharing}>
        <Text>{sharing ? "Preparing to share..." : "Share Image"}</Text>
      </Pressable>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Rather than converting the QR code SVG directly to a file, capturing the displayed View as is made it easier to create a shared image that includes the organization name and description.

References

Top comments (0)