Search code examples
react-nativereact-navigationback-button

How do I combine back buttons for Screens and WebView into one in React Native using React Navigation package?


I'm learning navigation in React Native, and can't figure out way how to conditionally use one Back Button. Wanted behaviour would be like this - if user navigate between screens, he can press Go Back button, but if one of the screens contains WebView and he already visited few pages there, user would go back through this webview pages first, then Screens. Hope that makes sense. Something like that: Screen A <- Screen B <- Screen C with WebView <- Webview Page 1 <- Webview Page 2

screenshot

AppScreen

import React from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';

import HomeScreen from './src/screens/HomeScreen';
import WebViewScreen from './src/screens/WebViewScreen';
import ParamsScreen from './src/screens/ParamsScreen';

const Stack = createNativeStackNavigator();

function App(): React.JSX.Element {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={
          ({navigation}) => {}
        }>
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{title: 'Customized Header Home'}}
        />
        <Stack.Screen
          name="WebView Screen"
          component={WebViewScreen}
          options={({navigation, route}) => ({
            title: 'Webview',
            headerLeft: () => (
              <Pressable onPress={() => navigation.goBack()}>
                <Text style={{color: 'blue'}}>Go Back within Screens</Text>
              </Pressable>
            ),
          })}
        />
        <Stack.Screen name="Params Screen" component={ParamsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({});

export default App;

WebViewScreen

import {StyleSheet, Text, View, ActivityIndicator, Button} from 'react-native';
import React, {useRef, useState} from 'react';
import {WebView} from 'react-native-webview';

const WebViewScreen = ({navigation, route}) => {
  const webViewRef = useRef<WebView>(null);

  return (
    <>
      <View style={{flex: 0, alignItems: 'center', justifyContent: 'center'}}>
        <Text>WebView Screen</Text>
        <Button
          title="Go Back within WebView"
          onPress={() => webViewRef.current?.goBack()}
        />
      </View>
      <WebView
        source={{uri: 'https://google.com'}}
        startInLoadingState
        ref={webViewRef}
        renderLoading={() => (
          <View style={{flex: 1, alignItems: 'center'}}>
            <ActivityIndicator size="large" />
          </View>
        )}
        allowsBackForwardNavigationGestures
        onNavigationStateChange={navState => {}}
      />
    </>
  );
};

export default WebViewScreen;

const styles = StyleSheet.create({});

At the moment I've managed to have two separate working buttons, but it would be much better to have one back button in the header. I was playing with Screen options and onNavigationStateChange params, and but kind of got lost there.

I guess I would need some kind of function like this:

  const goBack = () => {
if (webViewRef.current) {
  webViewRef.current.goBack();
} else {
  navigation.goBack();
}

};

but not sure how to integrate it with Stack.Navigator


Solution

  • You can achieve the desired behavior by using a combination of headerLeft and onNavigationStateChange in your WebViewScreen. Here's a modified version of your WebViewScreen that should help you implement the combined back functionality:

    import { StyleSheet, Text, View, ActivityIndicator, Button } from 'react-native';
    import React, { useRef } from 'react';
    import { WebView } from 'react-native-webview';
    
    const WebViewScreen = ({ navigation, route }) => {
      const webViewRef = useRef();
    
      const handleNavigationStateChange = (navState) => {
        // Check if WebView can go back
        const canGoBack = navState.canGoBack;
    
        // Update the header left button based on WebView navigation
        navigation.setOptions({
          headerLeft: () => (
            <View style={{ marginLeft: 10 }}>
              <Button
                onPress={() => {
                  if (canGoBack) {
                    // If WebView can go back, go back in WebView
                    webViewRef.current.goBack();
                  } else {
                    // If WebView cannot go back, go back in the navigation stack
                    navigation.goBack();
                  }
                }}
                title="Go Back"
                color="blue"
              />
            </View>
          ),
        });
      };
    
      return (
        <>
          <View style={{ flex: 0, alignItems: 'center', justifyContent: 'center' }}>
            <Text>WebView Screen</Text>
          </View>
          <WebView
            source={{ uri: 'https://google.com' }}
            startInLoadingState
            ref={webViewRef}
            renderLoading={() => (
              <View style={{ flex: 1, alignItems: 'center' }}>
                <ActivityIndicator size="large" />
              </View>
            )}
            allowsBackForwardNavigationGestures
            onNavigationStateChange={handleNavigationStateChange}
          />
        </>
      );
    };
    
    export default WebViewScreen;
    
    const styles = StyleSheet.create({});
    

    In this code, the handleNavigationStateChange function checks if the WebView can go back (canGoBack). It then updates the headerLeft button accordingly. If the WebView can go back, the button will trigger webViewRef.current.goBack(), otherwise, it will trigger navigation.goBack() to go back in the navigation stack.

    This way, you have a single "Go Back" button in the header that handles both WebView and screen navigation.