Updated July 17, 2019
Integrating React Navigation Back Button with a WebView
Let's say that you have an app that embeds a webview for an entire screen and the user can interact with it/navigate to subsequent navigation pages within it.
By default, if the user presses a back button they'll be taken back to the previous screen in your app - not the previous web page. That's not really intuitive for a user. Here's an example:
Starter Code
index.js
import React from 'react';
import { createAppContainer, createStackNavigator } from 'react-navigation';
import Index from './IndexScreen';
import Details from './DetailsScreen';
const App = createStackNavigator({
Index: {
screen: Index,
navigationOptions: {
headerTitle: 'Index',
},
},
Details: {
screen: Details,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Details',
}),
},
});
export default createAppContainer(App);
IndexScreen.js
import React from 'react';
import { View, Button } from 'react-native';
export default ({ navigation }) => (
<View>
<Button
title="Go To Details Screen"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
DetailsScreen.js
import React, { useRef } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { WebView } from 'react-native-webview';
export default ({ navigation }) => {
return (
<WebView
source={{ url: 'https://www.google.com/search?q=react+native+school' }}
startInLoadingState
renderLoading={() => (
<View style={{ flex: 1, alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
allowsBackForwardNavigationGestures
/>
);
};
Strategy
What we're going to do is hijack the "back" press in the navigator by passing our own press handler via params. We'll know what data we need to pass by leveraging the onNavigationStateChange
handler in a WebView.
Navigator Setup
From our component we're going to pass our data on the headerLeftInfo
param. If that param exists we'll pass along that title/onPress otherwise we'll use the default.
We can get the default React Navigation back button by import it from react-navigation
.
index.js
import React from 'react';
import {
createAppContainer,
createStackNavigator,
HeaderBackButton, // new import
} from 'react-navigation';
import Index from './IndexScreen';
import Details from './DetailsScreen';
const App = createStackNavigator({
Index: {
screen: Index,
navigationOptions: {
headerTitle: 'Index',
},
},
Details: {
screen: Details,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Details',
headerLeft: (props) => {
const headerLeftInfo = navigation.getParam('headerLeftInfo');
if (headerLeftInfo) {
return (
<HeaderBackButton
{...props}
title={headerLeftInfo.title}
onPress={headerLeftInfo.onPress}
/>
);
}
return <HeaderBackButton {...props} />;
},
}),
},
});
export default createAppContainer(App);
In our navigationOptions
we grab the param for headerLeftInfo
. If it exists we render the HeaderBackButton
with all the default props (so it behaves as normal as possible) and we override the title
and the onPress
.
If headerLeftInfo
doesn't exist then we just render the default button with the default props.
Passing Data to the Navigator from the Details Screen
To programmatically go back in our WebView we'll need to assign a ref on which we can call goBack
.
DetailsScreen.js
import React, { useRef } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { WebView } from 'react-native-webview';
export default ({ navigation }) => {
const ref = useRef(null);
return (
<WebView
ref={ref}
source={{ url: 'https://www.google.com/search?q=react+native+school' }}
startInLoadingState
renderLoading={() => (
<View style={{ flex: 1, alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
allowsBackForwardNavigationGestures
/>
);
};
Then we have to listen to onNavigationStateChange
on our WebView. It passes navState to the callback function. In that we care about the canGoBack
property which will let us know if there is a previous screen to go back to.
If so, use our custom button handler. If not, use the default button handler.
DetailsScreen.js
export default ({ navigation }) => {
const ref = useRef(null);
return (
<WebView
ref={ref}
source={{ url: 'https://www.google.com/search?q=react+native+school' }}
startInLoadingState
renderLoading={() => (
<View style={{ flex: 1, alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
allowsBackForwardNavigationGestures
onNavigationStateChange={(navState) => {
if (navState.canGoBack) {
navigation.setParams({
headerLeftInfo: {
title: '',
onPress: () => ref.current.goBack(),
},
});
} else {
navigation.setParams({
headerLeftInfo: null,
});
}
}}
/>
);
};
And there you have it!
Finished Code
index.js
import React from 'react';
import {
createAppContainer,
createStackNavigator,
HeaderBackButton,
} from 'react-navigation';
import Index from './IndexScreen';
import Details from './DetailsScreen';
const App = createStackNavigator({
Index: {
screen: Index,
navigationOptions: {
headerTitle: 'Index',
},
},
Details: {
screen: Details,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Details',
headerLeft: (props) => {
const headerLeftInfo = navigation.getParam('headerLeftInfo');
if (headerLeftInfo) {
return (
<HeaderBackButton
{...props}
title={headerLeftInfo.title}
onPress={headerLeftInfo.onPress}
/>
);
}
return <HeaderBackButton {...props} />;
},
}),
},
});
export default createAppContainer(App);
IndexScreen.js
import React from 'react';
import { View, Button } from 'react-native';
export default ({ navigation }) => (
<View>
<Button
title="Go To Details Screen"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
DetailsScreen.js
import React, { useRef } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { WebView } from 'react-native-webview';
export default ({ navigation }) => {
const ref = useRef(null);
return (
<WebView
ref={ref}
source={{ url: 'https://www.google.com/search?q=react+native+school' }}
startInLoadingState
renderLoading={() => (
<View style={{ flex: 1, alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
allowsBackForwardNavigationGestures
onNavigationStateChange={(navState) => {
if (navState.canGoBack) {
navigation.setParams({
headerLeftInfo: {
title: '',
onPress: () => ref.current.goBack(),
},
});
} else {
navigation.setParams({
headerLeftInfo: null,
});
}
}}
/>
);
};