Archive note: This post was originally published on DEV Community on February 18, 2020. It uses React Native 0.61.5, class components, and the package APIs available at that time. Keep the idea, but check each library’s current installation and permission requirements before reusing the code.

As a React Native developer, I regularly notice interesting features in the apps I use. Most of the time, I only think about how they might work and never try to build them.

That changes today.

React Native already provides tools such as Linking, for opening another application, and Share, for sending text through apps such as Telegram. This experiment goes one step further: capture a custom component as an image, preview it, and send that image through the native share sheet.

The implementation uses React Native Share and React Native View Shot.

Add the dependencies

Install both libraries, then update the iOS pods:

yarn add react-native-share react-native-view-shot
cd ios && pod install && cd ..

Build something to capture

To keep the example simple, I created a small card with React Native Paper:

<Card>
  <Card.Title
    title="This is an example"
    subtitle={this.state.subtitle}
    left={(props) => <Avatar.Icon {...props} icon="folder" />}
  />
  <Card.Content>
    <Title>Nicer title</Title>
    <Paragraph>I like trains</Paragraph>
  </Card.Content>
  <Card.Cover source={{ uri: "https://picsum.photos/700" }} />
  <Card.Actions>
    <Button onPress={this.takeSnapshot}>Take Snapshot</Button>
  </Card.Actions>
</Card>
An iPhone simulator showing a React Native Paper card with a title, landscape image, and Take Snapshot button.
The component that will become a shareable image.

Wrap the view

ViewShot identifies the part of the component tree that should appear in the image:

import ViewShot from "react-native-view-shot"

<ViewShot>
  <Card>{/* Card content */}</Card>
</ViewShot>

To capture it on demand, keep a reference to the wrapper:

card = React.createRef<ViewShot>()

<ViewShot ref={this.card}>
  <Card>
    {/* Card content */}
    <Button onPress={this.takeSnapshot}>Take Snapshot</Button>
  </Card>
</ViewShot>

Capture the snapshot

With the reference in place, captureRef can render the view into an image:

import ViewShot, { captureRef } from "react-native-view-shot"

takeSnapshot = async () => {
  if (!this.card.current) return

  const snapshot = await captureRef(this.card, {
    result: "data-uri",
  })

  this.setState({ snapshot })
}

The data-uri result is a Base64-encoded image string that includes its Data URI prefix. That makes it possible to display the result directly with React Native’s Image component:

<Image
  source={{ uri: this.state.snapshot }}
  resizeMode="contain"
  style={styles.snapshot}
/>
Animated iPhone demo showing a user capture the card and display its resulting image below.
Capturing the card and rendering its snapshot.

Open the native share sheet

Once a snapshot exists, pass its Data URI to React Native Share:

import Share from "react-native-share"

shareSnapshot = async () => {
  const { snapshot } = this.state
  if (!snapshot) return

  try {
    await Share.open({ url: snapshot })
  } catch (error) {
    console.log(error)
  }
}

<Button
  disabled={!this.state.snapshot}
  icon="share"
  mode="contained"
  onPress={this.shareSnapshot}
>
  Share
</Button>

And voilà.

Tapping Share opens the platform’s native share dialog, where the user can select a destination or cancel. Both paths need to be handled: cancellation may be reported through the same rejection path as an error, depending on the library version and options in use.

Animated iPhone demo showing a captured card image being sent through the native iOS share sheet.
Sharing the captured component through the native iOS dialog.

The original implementation is preserved in the accompanying GitHub pull request.