Editor’s note: This article was last updated by Joseph Mawa on 29 July 2024 to cover handling navigation and URL changes with React Native WebView, and implementing advanced customization techniques using WebView.
WebViews make it possible to embed web components in mobile applications. A web component can be anything from a simple HTML file to an entire webpage or application.
The react-native-webview package is a WebView component for React Native that allows you to embed web content in React Native applications. It is a free and open source package that is actively maintained by the React Native community. In this post, we will explore the most common use cases of React Native WebView.
Below, you can find the final result of our tutorial. We will display random programming languages, and, in the end, redirect to the LogRocket blog page:
To follow along, make sure you have installed the following:
Additionally, basic knowledge of JavaScript, React/React Native, and HTML is necessary.
I use Expo when developing React Native apps. The fastest way to create a new Expo app is to use create-expo-app
. This will create a new Expo project with all the necessary setup.
Navigate to your project directory and run the command below on the terminal:
npx create-expo-app@latest
This command will prompt you for the project name. After creating the project, it will also install the required dependencies.
Then, navigate to the newly created project directory and run npx expo start
or npm run start
to start the development server. Expo allows you to choose the device with which you are working: a physical Android/iOS device or an Android/iOS emulator/simulator.
Therefore, your development environment setup will depend on your choice of device. The Expo documentation has detailed setup guides.
Before we start our app, we still need to install the React Native WebView package. To do that, run the following command in your project directory:
expo install react-native-webview
This package will work both on Android and iOS devices.
Here is a short overview of the terminal commands:
# cd into the directory where to store your project $ cd dir # initialize the expo project $ npx create-expo-app@latest # navigate inside the newly created project $ cd my-project # install the webview package $ expo install react-native-webview # run the development server $ npx expo start
For simplicity, we will add our code to the app/(tabs)/index.tsx
file. We won’t create any additional files or include type annotations. Therefore, you can change the file extension from .tsx
to .jsx
if you don’t want to deal with TypeScript errors.
Delete everything in the app/(tabs)/index.tsx
file and then copy and paste the code below into it. Throughout this article, we will be modifying the app/(tabs)/index.tsx
file:
// app/(tabs)/index.jsx export default function HomeScreen() { return null; }
You should now have a blank homepage like the image below:
The simplest way to embed web content in a React Native application using React Native WebView is to provide the URL of the web content we want to embed:
# app/(tabs)/index.jsx import { WebView } from 'react-native-webview'; export default function HomeScreen() { return <WebView source={{ uri: "https://blog.logrocket.com/" }} />; }
In order to use the WebView
component, you have to import it as we did above. Then, all you need to do is set the source
prop.
Please note that the source
prop takes an object as its value. In this case, we are providing a URI
:
source={{ uri: 'https://blog.logrocket.com/' }}
The code above will result in something like this:
Another common way to embed web content in WebView is to use inline HTML. Let’s start with a very simple example here:
# app/(tabs)/index.jsx import { WebView } from "react-native-webview"; export default function HomeScreen() { const customHTML = ` <body style="display:flex; flex-direction: column;justify-content: center; align-items:center; background-color: black; color:white; height: 100%;"> <h1 style="font-size:100px; padding: 50px; text-align: center;" id="h1_element"> This is simple html </h1> <h2 style="display: block; font-size:80px; padding: 50px; text-align: center;" id="h2_element"> This text will be changed later! </h2> </body>`; return <WebView source={{ html: customHTML }} />; }
This will be the result of the code above:
The difference between this code and the one before is that we are now using inline HTML as the content of the WebView: source={{ html: customHTML }}
.
In the code above, we declared the customHTML
variable. It is a string containing h1
and h2
elements in a <body>
tag. It also has basic inline styles that center the elements and provide some background colors.
That is how you render inline HTML. But what if you want to include functionality that requires JavaScript, like dynamic features? Well, React Native WebView also offers a solution for that!
Let’s start with some code:
# app/(tabs)/index.jsx import { WebView } from "react-native-webview"; export default function HomeScreen() { const customHTML = ` <body style="display:flex; flex-direction: column;justify-content: center; align-items:center; background-color: black; color:white; height: 100%;"> <h1 style="font-size:100px; padding: 50px; text-align: center;" id="h1_element"> This is simple html </h1> <h2 style="display: block; font-size:80px; padding: 50px; text-align: center;" id="h2_element"> This text will be changed later! </h2> </body>`; const runFirst = ` setTimeout(function() { window.alert("Click me!"); document.getElementById("h1_element").innerHTML = "What is your favourite language?"; document.getElementById("h2_element").innerHTML = "We will see!"; }, 1000); true; // note: this is required, or you'll sometimes get silent failures `; const runBeforeFirst = ` window.isNativeApp = true; true; // note: this is required, or you'll sometimes get silent failures `; return ( <WebView source={{ html: customHTML }} onMessage={(event) => {}} injectedJavaScript={runFirst} injectedJavaScriptBeforeContentLoaded={runBeforeFirst} /> ); }
If you look at the WebView
component, you will notice that we have introduced three new props: onMessage
, injectedJavaScript
, and injectedJavaScriptBeforeContentLoaded
.
The JavaScript code passed to the injectedJavaScript
prop will be executed once, after the resource loads for the first time. Even if you refresh the site, the code will not be executed again!
In the runFirst
script, we trigger a window alert after 1s
. After the user closes the alert, we change the text of the h1
and h2
elements in the setTimeout
callback function.
On the other hand, the runBeforeFirst
script provided as the value of the injectedJavaScriptBeforeContentLoaded
prop will be executed before the page is loaded for the first time. It has no effect on the visuals of our app.
The onMessage
prop is required, even though the function we set as the value of this prop is empty at the moment. Without it, the scripts will not run!
The downside of the injectedJavaScript
and injectedJavaScriptBeforeContentLoaded
props is that they both run once. However, there are scenarios where you might need to execute your JavaScript code more than once. We’ll explore these situations in the next section!
injectJavaScript
method in React Native WebViewIn this section, we will implement the final features of the demo app: randomly displaying programming languages and redirecting to the LogRocket blog landing page:
# app/(tabs)/index.jsx import { WebView } from "react-native-webview"; import { useRef } from "react"; export default function HomeScreen() { const webRef = useRef(null); const customHTML = ` <body style="display:flex; flex-direction: column;justify-content: center; align-items:center; background-color: black; color:white; height: 100%;"> <h1 style="font-size:100px; padding: 50px; text-align: center;" id="h1_element"> This is simple html </h1> <h2 style="display: block; font-size:80px; padding: 50px; text-align: center;" id="h2_element"> This text will be changed later! </h2> </body>`; const runFirst = ` setTimeout(function() { window.alert("Click me!"); document.getElementById("h1_element").innerHTML = "What is your favourite language?"; document.getElementById("h2_element").innerHTML = "We will see!"; }, 1000); true; // note: this is required, or you'll sometimes get silent failures `; const runBeforeFirst = ` window.isNativeApp = true; true; // note: this is required, or you'll sometimes get silent failures `; const injectedJavaScript = ` const languages = [ "Rust", "Python", "JavaScript", "TypeScript", "C++", "Go", "R", "Java", "PHP", "Kotlin", ]; const headerElement = document.getElementById("h2_element"); let counter = 0; const setIntervalId = setInterval(() => { if (counter === languages.length) { clearInterval(setIntervalId); window.ReactNativeWebView.postMessage( "You are now getting redirected!" ); window.location = "https://blog.logrocket.com"; return; } if (document.body.style.backgroundColor === "white") { document.body.style.backgroundColor = "black"; document.body.style.color = "white"; } else { document.body.style.backgroundColor = "white"; document.body.style.color = "black"; } headerElement.textContent = languages[counter++] + "?"; window.ReactNativeWebView.postMessage("counter: " + counter.toString()); }, 1_000); true; // note: this is required, or you'll sometimes get silent failures `; const onLoadHandler = ({ nativeEvent }) => { if (!nativeEvent.url.startsWith("http")) { webRef.current.injectJavaScript(injectedJavaScript); } }; return ( <WebView source={{ html: customHTML }} ref={webRef} onMessage={(event) => { console.log(event.nativeEvent.data); }} onLoad={onLoadHandler} injectedJavaScript={runFirst} injectedJavaScriptBeforeContentLoaded={runBeforeFirst} /> ); }
In the code above, we declared a useRef
Hook for referencing the WebView
component. In the onLoad
event handler, we are imperatively injecting code using the injectJavaScript
method after the WebView has fully loaded:
webRef.current.injectJavaScript(injectedJavaScript);
In the injected code, we select the programming languages at intervals of one second in the order in which they appear in the array and display each one in the web content:
document.getElementById("h2_element").innerHTML = "${selectedLanguage}?";
Additionally, each time the background changes from black to white (or the other way around), we need the font color to change, too — but this will only happen ten times. After the tenth execution, the script redirects to the LogRocket blog landing page. That is how you inject code into web content using the injectJavaScript
method.
But we’re not done yet! Maybe you noticed that the onMessage
prop we passed to the WebView
component has changed, too. You can see that we are logging some information.
Until now, we have only sent information from our app to the webpage. But what if we wanted to receive information from the webpage? This is where the onMessage
prop comes in.
We are already logging data from the webpage in the onMessage
function we passed to the WebView
component. The JavaScript code we injected into the webpage sends back the message like so:
window.ReactNativeWebView.postMessage("counter: ${counter}");
This is what our demo app will now look like:
Sometimes you may want to monitor URL changes in a WebView and take appropriate action when the URL changes. This is useful for real-time monitoring, tracking user interaction, and safelisting or blocklisting certain URLs.
React Native WebView has several built-in APIs for handling navigation and URL changes. You can use the onNavigationStateChange
function to monitor navigation and changes in the URL. It is invoked whenever the WebView
loading starts and ends:
<WebView source={{ uri: 'https://blog.logrocket.com/' }} onNavigationStateChange={(navState) => { console.log(navState) }} />
The navState
parameter is an object that includes the following properties. You can use it to monitor URL changes and manage loading states:
{ url: string; loading: boolean; title: string; canGoBack: boolean; canGoForward: boolean; }
Similarly, you can also use the onShouldStartLoadWithRequest
function to manage network requests within the WebView. It comes in handy for monitoring network requests and restricting navigation.
The callback function should return true
to continue loading the request, otherwise, it should return false
. The example below will restrict navigation within the loaded website:
<WebView source={{ uri: 'https://blog.logrocket.com/' }} onShouldStartLoadWithRequest={(request) => { return request.url.startsWith('https://blog.logrocket.com/'); }} />
More often than not, you may want to customize the loaded web content to match your branding or monitor network requests and navigation.
As explained in the previous sections, you can inject JavaScript code that modifies the loaded web content. To change the styling of the web content, the injected JavaScript code can create a style
element and append it to the document’s head
element like so:
import { WebView } from "react-native-webview"; export default function HomeScreen()() { const injectedJavaScript = ` const style = document.createElement('style'); style.innerHTML = 'body { background-color: grey; }'; document.head.appendChild(style); true; `; return ( <WebView source={{ uri: "https://blog.logrocket.com/" }} injectedJavaScript={injectedJavaScript} /> ); }
To provide a better user experience, you can also track loading states and display a loading indicator while loading web content. As an example, you can display React Native’s built-in ActivityIndicator
while the web content is loading, and render the WebView after it loads successfully, as in the example below:
export default function HomeScreen() = () => { return ( <WebView source={{ uri: "https://blog.logrocket.com/" }} startInLoadingState={true} renderLoading={() => <ActivityIndicator size="large" />} /> ); };
The renderLoading
prop is a function that returns a loading indicator. In the above example, it returns the built-in ActivityIndicator
component. You can also render a custom component like so:
<WebView source={{ uri: "https://blog.logrocket.com/" }} startInLoadingState={true} renderLoading={() => <LoadingIndicator />} />
When using the renderLoading
props as in the above example, you should also set the startInLoadingState
to true
.
Similar to managing a loading indicator, you can use the renderError
prop to manage errors. If an error occurs, the function you pass as the value of the renderError
prop is invoked with the name of the error as an argument:
export default function HomeScreen() = () => { const [error, setError] = useState(null); return ( <> {error ? ( <Error errorMessage={error.message} /> ) : ( <WebView source={{ uri: "https://blog.logrocket.com/" }} onError={(error) => setError(error)} startInLoadingState={true} renderLoading={() => <ActivityIndicator size="large" />} /> )} </> ); };
The example above will render a loading indicator while the WebView is loading. If an error occurs, it updates the state and renders a custom error component.
WebViews come in handy for embedding web content in mobile applications. In React Native, you can use the react-native-webview package to achieve this.
To use React Native WebView to embed a web component, you either provide a URL for the web content or embed an inline HTML. The react-native-webview package has several built-in features, including those for injecting JavaScript code, managing navigation and URL changes within the WebView, and customizing the loaded web content.
Feel free to use the code we used in this article as a foundation for further development. You can find its source code on GitHub.
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowMaking carousels can be time-consuming, but it doesn’t have to be. Learn how to use React Snap Carousel to simplify the process.
Consider using a React form library to mitigate the challenges of building and managing forms and surveys.
In this article, you’ll learn how to set up Hoppscotch and which APIs to test it with. Then we’ll discuss alternatives: OpenAPI DevTools and Postman.
Learn to migrate from react-native-camera to VisionCamera, manage permissions, optimize performance, and implement advanced features.
2 Replies to "React Native WebView: A complete guide"
Thanks for this post. Is there any way to use a JS remote libray in React Native Webview (e.g. Jquery)? I’ve been struggling with this for a while now but can’t get it work yet.
Is there any issues if the webpage is requesting for camera and mic permission? Android certainly seems to block the permission always. Tested this site https://webcammictest.com/ as source on webview. Woking fine on iOS