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:

The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
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's Galileo AI watches sessions for you and and surfaces the technical and usability issues holding back 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.

Rosario De Chiara discusses why small language models (SLMs) may outperform giants in specific real-world AI systems.

Vibe coding isn’t just AI-assisted chaos. Here’s how to avoid insecure, unreadable code and turn your “vibes” into real developer productivity.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.
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 now
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