Skip to main content
TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸
react-native

How to Handle Deep Linking in a React Native App

Aland Baban · March 2022 · 5 min read
[ contents ]

Note: This tutorial was published in 2022 and uses React Navigation v6.

While the core concepts of deep linking and navigation remain the same, always refer to the official React Navigation documentation for the latest API usage and configuration best practices.

Deep linking allows a URL to open a specific screen within a mobile application, rather than just launching the app to its home screen. This technique is essential for creating seamless user experiences, particularly for marketing campaigns, notifications, and web-to-app user flows.

This tutorial demonstrates how to implement and handle deep links in a React Native application using the React Navigation library (v6). We will build a simple two-screen application to showcase the configuration process for both iOS and Android.

The source code for the example app is available at this GitHub Repo.

Setting up Navigation

First, create a new React Native application.

code
npx react-native init rnDeepLinking
cd rnDeepLinking

Next, install the required dependencies for React Navigation v6.

code
yarn add @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context

For iOS, you must install the pods.

code
npx pod-install ios

For Android, additional configuration is required in android/app/src/main/java/com/rndeeplinking/MainActivity.java. Add the following onCreate method:

code
package com.rndeeplinking;

import android.os.Bundle;
import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {
  // ...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
  }
}

Creating the App Screens

Create a src/screens directory with two files: HomeScreen.js and DetailsScreen.js.

The HomeScreen.js will fetch and display a list of users from the JSON Placeholder API. Each item will be pressable to navigate to the details screen.

code
// src/screens/HomeScreen.js
import React, { useState, useEffect } from 'react';
import { ActivityIndicator, View, Text, FlatList, Pressable } from 'react-native';
import Separator from '../components/Separator';

const HomeScreen = ({ navigation }) => {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(res => {
        setData(res);
        setIsLoading(false);
      });
  }, []);

  const renderList = ({ item }) => (
    <Pressable
      onPress={() => navigation.navigate('Details', { personDetailsId: item.id })}
      style={{ paddingHorizontal: 10 }}
    >
      <Text style={{ fontSize: 24, color: '#000' }}>{item.name}</Text>
    </Pressable>
  );

  return (
    <View style={{ flex: 1 }}>
      {isLoading ? (
        <ActivityIndicator color="blue" size="large" />
      ) : (
        <FlatList
          data={data}
          keyExtractor={item => item.id}
          ItemSeparatorComponent={Separator}
          renderItem={renderList}
        />
      )}
    </View>
  );
};
export default HomeScreen;

Create a simple Separator component at src/components/Separator.js for the list.

The DetailsScreen.js will initially just display placeholder text.

code
// src/screens/DetailsScreen.js
import React from 'react';
import { View, Text } from 'react-native';

const DetailsScreen = () => {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Details Screen</Text>
    </View>
  );
};
export default DetailsScreen;

Configuring the Stack Navigator

Create a src/navigation/RootNavigator.js file to define the stack navigator.

code
// src/navigation/RootNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from '../screens/HomeScreen';
import DetailsScreen from '../screens/DetailsScreen';

const RootStack = createNativeStackNavigator();

const RootNavigator = () => (
  <NavigationContainer>
    <RootStack.Navigator>
      <RootStack.Screen name="Home" component={HomeScreen} />
      <RootStack.Screen name="Details" component={DetailsScreen} />
    </RootStack.Navigator>
  </NavigationContainer>
);

export default RootNavigator;

Render this RootNavigator in App.js. After building the app, you should have a functioning two-screen application.

Configuring Deep Linking

To enable deep linking, you need to provide a linking configuration object to the NavigationContainer. This object defines the URI schemes your app will respond to.

In RootNavigator.js, define the linking object:

code
import { ActivityIndicator } from 'react-native';

const linking = {
  prefixes: ['peoplesapp://'],
  config: {
    initialRouteName: 'Home',
    screens: {
      Home: {
        path: 'home'
      },
      Details: {
        path: 'details/:personId' // Add a dynamic parameter
      }
    }
  }
};

const RootNavigator = () => (
  <NavigationContainer
    linking={linking}
    fallback={<ActivityIndicator color="blue" size="large" />}
  >
    {/* ... Navigator ... */}
  </NavigationContainer>
);

Adding the URI Scheme to Native Projects

The URI scheme must be registered in the native iOS and Android projects.

Using uri-scheme Package

The easiest way to configure the schemes is with the uri-scheme package.

code
# For iOS
npx uri-scheme add peoplesapp --ios

# For Android
npx uri-scheme add peoplesapp --android

Rebuild your app after running these commands.

Manual iOS Configuration

To manually configure iOS, first add the RCTLinkingManager to AppDelegate.m. Then, in Xcode, navigate to your project's Info tab and add a URL Type, setting both the Identifier and URL Schemes to peoplesapp.

Manual Android Configuration

For Android, open android/app/src/main/AndroidManifest.xml. Set launchMode="singleTask" on the <activity> tag and add a new <intent-filter> to handle your custom scheme.

code
<activity
  android:name=".MainActivity"
  android:launchMode="singleTask"
  ...>
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="peoplesapp" />
  </intent-filter>
</activity>

Rebuild your app after making these changes.

You can test your deep links from the command line.

For iOS:

code
xcrun simctl openurl booted peoplesapp://details/1

For Android:

code
adb shell am start -W -a android.intent.action.VIEW -d "peoplesapp://details/1"

These commands should open your app directly to the Details screen.

Handling Dynamic Parameters

The deep link path details/:personId includes a dynamic parameter. This parameter is accessible in the screen component via route.params.

Modify DetailsScreen.js to fetch data based on the ID passed either through navigation params (personDetailsId) or deep link params (personId).

code
// src/screens/DetailsScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';

const DetailsScreen = ({ route }) => {
  const params = route.params || {};
  const { personDetailsId, personId } = params;
  const idToFetch = personId || personDetailsId;

  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    if (idToFetch) {
      fetch(`https://jsonplaceholder.typicode.com/users/${idToFetch}`)
        .then(res => res.json())
        .then(res => {
          // Format data for display
          const fetchedDetails = Object.keys(res).map(key => ({ key, value: `${res[key]}` }));
          setData(fetchedDetails);
        })
        .finally(() => setIsLoading(false));
    }
  }, [idToFetch]);

  return (
    <View style={{ flex: 1, paddingTop: 10, paddingHorizontal: 10 }}>
      {isLoading ? (
        <ActivityIndicator color="blue" size="large" />
      ) : (
        data.map(person => (
          <Text key={person.key}>{`${person.key}: ${person.value}`}</Text>
        ))
      )}
    </View>
  );
};

export default DetailsScreen;

Now, whether you navigate from the home screen or open a deep link, the DetailsScreen will fetch and display the correct user's information.

Conclusion

This tutorial provided a comprehensive walkthrough of configuring deep linking in a React Native application using React Navigation. You learned how to define URL schemes, configure the navigation container, and handle both static and dynamic route parameters.

Implementing deep linking can significantly improve your application's user experience and is a key feature for user engagement and retention.