Intro
This article is quite extensive, but for good reason: it covers important, non-trivial topics and provides a detailed walkthrough of building a fully functional app from start to finish. Although the app itself is simple in nature, every part of its code is carefully explained, making it an excellent reference for developers who want to understand the complete lifecycle of a React Native project.
By the end of this article, you'll not only have a working app but also a solid understanding of how to customize and expand upon it for more complex applications.
Prerequisites
We will use Expo and the realtivi tools for this project. We will use the expo app to avoid unnecessary compilation, also we will use a real device with the help of a screen duplicator like scrcpy. The installation of node and scrcpy varies greatly from one operating system to another, but I always recommend using a package manager such as apt on ubuntu, scoop on windows, homebrew on linux.
Below are the detailed steps to install Node.js and scrcpy on the three major operating systems: Ubuntu, Windows, and macOS.
You can install Node.js using the NodeSource repository or directly with apt. Here's the method using the NodeSource repository to get the latest stable version:
# Update system and install required dependencies
sudo apt update
sudo apt install -y curl
# Add NodeSource APT repository for Node.js 18.x
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
# Install Node.js
sudo apt install -y nodejs
# Verify the installation
node -v
npm -vTo install scrcpy, you can directly use the apt package manager:
# Install scrcpy
sudo apt install scrcpy
# Verify the installation
scrcpy --versionBefore using scoop, you need to have it installed. If it's not installed yet, run the following PowerShell command:
# Install Scoop
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
irm get.scoop.sh | iexAfter scoop is installed, use it to install Node.js:
# Install Node.js
scoop install nodejs-lts
# Verify the installation
node -v
npm -vTo install scrcpy with scoop, use the following command:
# Install scrcpy
scoop install scrcpy
# Verify the installation
scrcpy --versionIf you don't already have Homebrew installed, you can install it by running:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Once Homebrew is installed, you can install Node.js:
# Install Node.js
brew install node
# Verify the installation
node -v
npm -vTo install scrcpy with Homebrew, use this command:
# Install scrcpy
brew install scrcpy
# Verify the installation
scrcpy --version- Ubuntu: Install Node.js via NodeSource repository and scrcpy using
apt. - Windows: Use
scoopto install both Node.js and scrcpy. - macOS: Use
homebrewto install Node.js and scrcpy.
This ensures that both Node.js and scrcpy are properly installed across all three platforms.
Start by creating a new Expo project. Use the following command to generate a blank project named WebBrowser:
npx create-expo-app@latest -t blank WebBrowserThis will set up a basic Expo project structure that you can customize further.
After creating the project, you need to install some dependencies that will handle web content rendering, safe areas, and manage the user interface.
Install WebView to render web content inside the app:
npx expo install react-native-webviewInstall Safe Area Context to ensure proper layout handling within the safe areas of devices:
npx expo install react-native-safe-area-contextInstall Async Storage, which allows you to persist data asynchronously, such as user preferences or temporary data:
npx expo install @react-native-async-storage/async-storageLastly, install Expo Status Bar to manage and configure the status bar on both iOS and Android:
npx expo install expo-status-barFor state management, it's recommended to use Zustand, a lightweight library for managing global state. Install it using:
npm install zustandBy following these steps, you'll have your Expo project fully set up with all the necessary UI and state management dependencies, ready for development!
In today's mobile-centric world, web browsers are indispensable tools that enable us to access a vast array of information and services on the go. While popular browsers like Chrome and Safari dominate the market, creating a custom web browser can be an exciting project that enhances your understanding of mobile app development and web technologies. This article serves as the first in a series that guides you through building a simple web browser using React Native.
Developing a web browser from scratch is an excellent way to deepen your knowledge of:
- React Native Framework: Gain hands-on experience with React Native components, navigation, and state management.
- Web Technologies: Understand how web content is rendered and interacted with on mobile devices.
- State Management: Learn how to manage complex state using modern libraries.
Creating your own browser allows you to:
- Implement Custom Features: Add functionalities tailored to specific needs, such as specialized bookmarking or content filtering.
- Design Unique User Interfaces: Create an interface that aligns with a particular aesthetic or user experience.
- Control Privacy and Security: Incorporate privacy features that give users more control over their data.
For developers, having a custom-built browser in your portfolio showcases your skills in:
- Full-Stack Development: Demonstrates proficiency in both front-end and back-end aspects.
- Problem-Solving: Highlights your ability to tackle complex projects.
- Innovation: Shows initiative and creativity in developing unique applications.
The goal of this project is to build a functional web browser for mobile devices using React Native. The browser will include essential features such as:
- Web Page Rendering: Display web content using the WebView component.
- Navigation Controls: Implement back, forward, and refresh buttons.
- URL Input: Allow users to enter web addresses.
- Browsing History: Keep track of visited URLs and provide an interface to revisit them.
- State Management: Use a global state management library to handle the application's state efficiently.
- React Native: A JavaScript framework for building native mobile apps.
- WebView Component: A React Native component that renders web content.
- Zustand: A lightweight state management library.
- AsyncStorage: For data persistence across app sessions.
The project will be divided into several components:
- Web Browser Component: Handles the display of web content and navigation controls.
- History Component: Manages the browsing history and provides a user interface to access it.
- State Management: Utilizes Zustand to manage the application's state globally.
- User Interface: Focuses on the layout, styling, and interaction patterns.
- Web Navigation: Users can navigate to different web pages using URL input and navigation buttons.
- History Management: The app records visited URLs and allows users to view and clear their browsing history.
- Persistent Storage: Browsing history and other state data persist between app sessions using AsyncStorage.
- Responsive Design: The interface adapts to different screen sizes and orientations.
By the end of this project, you will have:
- Enhanced Understanding of React Native: Deepened your knowledge of building mobile applications using React Native components and APIs.
- Experience with State Management: Gained practical experience in managing application state with Zustand.
- Knowledge of Web Content Rendering: Learned how to render and interact with web content within a mobile app.
- Skills in Data Persistence: Understood how to use AsyncStorage for storing data locally on the device.
- Improved UI/UX Design Skills: Developed a user-friendly interface that offers a seamless browsing experience.
State management is a crucial part of any application, especially when dealing with complex components that need to share data. Zustand is a lightweight state management library that provides a simple and intuitive API, making it an excellent choice for our browser project.
Zustand offers a minimalistic approach to state management without the need for extensive boilerplate code. It allows you to create a global store with ease and provides a straightforward way to access and update the state across your components.
Zustand is built with performance in mind. It leverages React's context API under the hood but optimizes it to prevent unnecessary re-renders, ensuring that your application remains responsive.
With Zustand, you can structure your state in a way that makes sense for your application. It doesn't enforce a specific pattern, allowing you to keep your state management logic clean and maintainable.
The store is the central place where all the state variables and actions are defined. In our project, we create the store in a file named browser.js within a store directory.
To create the store, we use the create function from Zustand:
// store/browser.js
import { create } from 'zustand';
const useBrowserStore = create((set, get) => ({
// State variables and actions
}));
export default useBrowserStore;We want our browser to remember the browsing history even after the app is closed. To achieve this, we use the persist middleware from Zustand along with AsyncStorage:
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
const useBrowserStore = create(
persist(
(set, get) => ({
// State variables and actions
}),
{
name: 'browser-storage',
storage: createJSONStorage(() => AsyncStorage),
},
),
);By configuring the store with persist, we ensure that the state is saved to the device's local storage and rehydrated when the app restarts.
Our browser needs to manage several pieces of state to function correctly. Below are the state variables defined in our store:
- Type:
string - Initial Value:
'https://www.example.com' - Purpose: Holds the value of the URL input field where the user can type or edit the web address.
- Type:
string - Initial Value:
'https://www.example.com' - Purpose: Represents the actual URL that the
WebViewcomponent will load. This value is updated when the user submits the URL they have entered.
- Type:
boolean - Initial Value:
false - Purpose: Indicates whether the web content is currently loading. This is used to display a loading indicator to the user.
- Type:
boolean - Initial Value:
false - Purpose: Determines if the browser can navigate back in the history stack. This state controls the enabled or disabled state of the back navigation button.
- Type:
boolean - Initial Value:
false - Purpose: Determines if the browser can navigate forward in the history stack. This state controls the enabled or disabled state of the forward navigation button.
- Type:
array - Initial Value:
[] - Purpose: Stores the list of URLs that the user has visited. This array is used to display the browsing history and allows the user to revisit previous pages.
- Type:
boolean - Initial Value:
false - Purpose: Controls whether the history component is displayed. When set to
true, the history screen is shown instead of the browser.
Actions are functions that modify the state. They are crucial for updating the store in response to user interactions or other events.
- Parameters:
inputUrl(string) - Purpose: Updates the
inputUrlstate with the value provided by the user as they type in the URL input field.
- Parameters:
webUrl(string) - Purpose: Updates the
webUrlstate when the user submits the URL. This triggers theWebViewto load the new page.
- Parameters:
isLoading(boolean) - Purpose: Sets the
isLoadingstate to indicate whether the web content is currently loading.
- Parameters:
canGoBack(boolean) - Purpose: Updates the
canGoBackstate based on the navigation history. This enables or disables the back navigation button accordingly.
- Parameters:
canGoForward(boolean) - Purpose: Updates the
canGoForwardstate based on the navigation history. This enables or disables the forward navigation button accordingly.
- Parameters:
showHistory(boolean) - Purpose: Controls the visibility of the history component. When set to
true, the browser component is hidden, and the history component is displayed.
- Parameters:
history(array) - Purpose: Replaces the current history array with a new one. This action is useful when clearing the history or restoring it from persistent storage.
-
Parameters:
url(string) -
Purpose: Adds a new URL to the
historyarray. Before adding, it checks if the URL is already present to avoid duplicates. -
Implementation Details:
addToHistory: (url) => { const { history } = get(); if (!history.includes(url)) { set({ history: [...history, url] }); } },
This function retrieves the current
historyfrom the store usingget(). It checks if the URL is not already included in thehistoryarray and then updates the state with the new history list.
-
Purpose: Clears the browsing history by setting the
historyarray to an empty array. -
Implementation Details:
clearHistory: () => set({ history: [] }),
By exporting useBrowserStore, we can use this hook inside our React components to access and manipulate the state.
Example Usage in a Component:
import useBrowserStore from '../store/browser';
const Component = () => {
const { inputUrl, setInputUrl } = useBrowserStore();
return (
<TextInput
value={inputUrl}
onChangeText={setInputUrl}
// other props
/>
);
};In this example, we import the store and destructure inputUrl and setInputUrl to use within the component. This pattern is repeated across different components to access the necessary state and actions.
- Global State Access: Any component can access the store without prop drilling.
- Separation of Concerns: The store handles the state logic, while components focus on the UI.
- Ease of Maintenance: Having all state-related logic in one place simplifies debugging and future modifications.
- Persisted State: With
persistandAsyncStorage, the state remains consistent across app sessions.
- Avoid Storing Component References: Keep UI-specific references like
reflocal to the components and avoid storing them in the global state. - Immutable Updates: Always ensure that state updates are performed immutably to prevent unintended side effects.
- Selective State Updates: Update only the parts of the state that need to change to optimize performance.
Understanding the state management layer is crucial before moving on to building the user interface, as it ensures that the UI components can interact seamlessly with the state. In the next article, we will focus on constructing the UI components, integrating them with our store, and explaining the code in detail.
Now, we'll focus on constructing the user interface (UI), exploring the components used, and providing a detailed explanation of the code.
Our project's structure is organized as follows:
- components/
- History.jsx: Component for displaying browsing history.
- WebBrowser.jsx: Main browser component handling web navigation.
- store/
- browser.js: Zustand store managing the application's state.
The WebBrowser component serves as the primary interface for the user to interact with web content. It includes:
- Navigation Bar: Contains buttons for back, forward, refresh, and history, as well as the URL input field.
- WebView: Renders the web pages.
- State Management: Connects to the Zustand store to access and update the state.
The History component displays the list of visited URLs and allows the user to revisit or clear their browsing history.
- History List: Shows the URLs in a scrollable view.
- Clear History Button: Allows users to clear their browsing history.
- Close Button: Returns the user to the
WebBrowsercomponent.
Let's dive into the code for each component, explaining how they work and how they integrate with the store.
// WebBrowser.jsx
import React, { useRef, useEffect } from 'react';
import {
View,
TextInput,
StyleSheet,
ActivityIndicator,
TouchableOpacity,
BackHandler,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { WebView } from 'react-native-webview';
import { MaterialIcons } from '@expo/vector-icons';
import useBrowserStore from '../store/browser';
import HistoryComponent from './History';
const WebBrowser = () => {
// Destructure states and actions from the store
const {
inputUrl,
setInputUrl,
webUrl,
setWebUrl,
isLoading,
setIsLoading,
canGoBack,
setCanGoBack,
canGoForward,
setCanGoForward,
showHistory,
setShowHistory,
addToHistory,
} = useBrowserStore();
const webviewRef = useRef(null);
const iconColor = 'white';
// Navigation functions
const goBack = () => {
if (webviewRef.current && canGoBack) webviewRef.current.goBack();
};
const goForward = () => {
if (webviewRef.current && canGoForward) webviewRef.current.goForward();
};
const reloadPage = () => {
if (webviewRef.current) webviewRef.current.reload();
};
// Handle URL submission
const onSubmitEditing = () => {
let newUrl = inputUrl.trim();
if (!/^https?:\/\//i.test(newUrl)) {
newUrl = 'https://' + newUrl;
}
setWebUrl(newUrl);
};
// Update state on navigation change
const onNavigationStateChange = (navState) => {
setInputUrl(navState.url);
setCanGoBack(navState.canGoBack);
setCanGoForward(navState.canGoForward);
// Update history
addToHistory(navState.url);
};
// Handle Android back button
useEffect(() => {
const onBackPress = () => {
if (webviewRef.current && canGoBack) {
webviewRef.current.goBack();
return true; // Event handled
}
return false; // Allow default behavior
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => {
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
};
}, [canGoBack]);
return (
<SafeAreaView style={{ flex: 1 }}>
{!showHistory && (
<>
<View style={styles.navigationBar}>
{/* Back Button */}
<TouchableOpacity
onPress={goBack}
style={styles.iconButton}
disabled={!canGoBack}
>
<MaterialIcons
name="arrow-back"
size={24}
color={canGoBack ? iconColor : 'lightgray'}
/>
</TouchableOpacity>
{/* Forward Button */}
<TouchableOpacity
onPress={goForward}
style={styles.iconButton}
disabled={!canGoForward}
>
<MaterialIcons
name="arrow-forward"
size={24}
color={canGoForward ? iconColor : 'lightgray'}
/>
</TouchableOpacity>
{/* URL Input */}
<TextInput
style={styles.urlInput}
value={inputUrl}
onChangeText={setInputUrl}
onSubmitEditing={onSubmitEditing}
placeholder="Enter URL"
placeholderTextColor="white"
keyboardType="url"
autoCapitalize="none"
autoCorrect={false}
/>
{/* Refresh Button */}
<TouchableOpacity onPress={reloadPage} style={styles.iconButton}>
<MaterialIcons name="refresh" size={24} color={iconColor} />
</TouchableOpacity>
{/* History Button */}
<TouchableOpacity
onPress={() => setShowHistory(true)}
style={styles.iconButton}
>
<MaterialIcons name="history" size={24} color={iconColor} />
</TouchableOpacity>
</View>
{/* Loading Indicator */}
{isLoading && (
<View style={styles.loadingIndicator}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
)}
{/* WebView */}
<WebView
source={{ uri: webUrl }}
ref={webviewRef}
onNavigationStateChange={onNavigationStateChange}
onLoadStart={() => setIsLoading(true)}
onLoadEnd={() => setIsLoading(false)}
style={{ flex: 1 }}
/>
</>
)}
{showHistory && <HistoryComponent />}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
navigationBar: {
flexDirection: 'row',
alignItems: 'center',
padding: 5,
backgroundColor: '#1E90FF',
},
iconButton: {
padding: 5,
},
urlInput: {
flex: 1,
height: 40,
borderColor: 'white',
borderWidth: 1,
paddingHorizontal: 5,
marginHorizontal: 5,
borderRadius: 5,
color: 'white',
backgroundColor: '#4682B4',
},
loadingIndicator: {
position: 'absolute',
top: '50%',
left: '50%',
marginLeft: -25,
marginTop: -25,
zIndex: 1,
},
});
export default WebBrowser;The component begins with several essential import statements. React, useRef, and useEffect are brought in for managing component logic and lifecycle events. Various React Native components like View, TextInput, and TouchableOpacity are used to construct the UI, while WebView from react-native-webview is used to display web content. MaterialIcons from @expo/vector-icons provides navigation icons, and useBrowserStore from the Zustand store is used for state management. The HistoryComponent is also imported to show browsing history.
State variables and actions are extracted from the store with useBrowserStore(), and a webviewRef is created for controlling the WebView component. Key navigation functions include goBack(), goForward(), and reloadPage(), which handle back/forward navigation and page reloading. The URL submission is managed by onSubmitEditing(), which formats the URL and updates the store's webUrl.
When the web view navigates to a new page, onNavigationStateChange() updates the app's state with the current URL, navigation capabilities, and history. An useEffect hook listens for the hardware back button on Android, navigating back within the web view when possible.
The app conditionally renders either the browser UI or the history component, depending on the value of showHistory. The navigation bar features buttons for back and forward navigation, a TextInput field for entering URLs, and additional buttons for refreshing the page and accessing the browsing history. The buttons are interactive and are disabled when back or forward navigation is unavailable.
When the web content is loading, an ActivityIndicator is displayed, signaling that the page is in the process of loading. The WebView component itself renders the web content and listens for changes in navigation state to update the app's internal state.
Finally, StyleSheet is used to style components such as the navigation bar, buttons, and loading indicators, ensuring that the UI is clean and user-friendly.
in detail:
Import Statements
- React and Hooks: Import
React,useRef, anduseEffectfor component logic and lifecycle management. - React Native Components: Import various components like
View,TextInput,TouchableOpacity, etc. - WebView: Import
WebViewfromreact-native-webviewto render web content. - Icons: Use
MaterialIconsfrom@expo/vector-iconsfor navigation icons. - State Management: Import
useBrowserStorefrom the Zustand store. - History Component: Import
HistoryComponentfor displaying browsing history.
State and Actions
Destructure the necessary state variables and actions from the store using useBrowserStore().
Refs
- webviewRef: A reference to the
WebViewcomponent, used for controlling navigation.
Navigation Functions
- goBack(): Navigates back in the web history if possible.
- goForward(): Navigates forward in the web history if possible.
- reloadPage(): Reloads the current web page.
URL Submission Handling
- onSubmitEditing(): Handles the event when the user submits the URL in the input field. It ensures that the URL is correctly formatted and updates
webUrlin the store.
Navigation State Change
- onNavigationStateChange(navState): Updates the state based on the navigation state of the
WebView, such as updatinginputUrl,canGoBack,canGoForward, and adding the URL to history.
Back Button Handling
- useEffect Hook: Listens for the hardware back button press on Android devices. If the web view can go back, it navigates back; otherwise, it allows the default behavior.
Conditional Rendering
- showHistory: If
showHistoryisfalse, the browser UI is rendered. Iftrue, theHistoryComponentis displayed.
Navigation Bar
- Back and Forward Buttons: Use
TouchableOpacityandMaterialIconsto create interactive buttons, which are disabled based oncanGoBackandcanGoForward. - URL Input Field: A
TextInputcomponent bound toinputUrl, allowing the user to enter and submit URLs. - Refresh and History Buttons: Additional navigation controls.
Loading Indicator
- Displays an
ActivityIndicatorwhenisLoadingistrue.
WebView Component
- Renders the web content based on
webUrl. - Uses
webviewReffor navigation control. - Listens to navigation state changes to update the store.
Styling
- Uses
StyleSheetto style components such as the navigation bar, buttons, and loading indicator.
// History.jsx
import React from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
Alert,
} from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import Constants from 'expo-constants';
import useBrowserStore from '../store/browser';
const HistoryComponent = () => {
const { history, setWebUrl, setInputUrl, setShowHistory, clearHistory } =
useBrowserStore();
// Load a URL from history
const loadFromHistory = (url) => {
setWebUrl(url);
setInputUrl(url);
setShowHistory(false); // Close the history view
};
// Confirm and clear history
const confirmClearHistory = () => {
Alert.alert(
'Confirm',
'Are you sure you want to clear your browsing history?',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'OK', onPress: clearHistory },
],
{ cancelable: true },
);
};
return (
<View style={styles.historyContainer}>
{/* Header */}
<View style={styles.historyHeader}>
<Text style={styles.historyTitle}>History</Text>
<TouchableOpacity
onPress={() => setShowHistory(false)} // Close history view
style={styles.iconButton}
>
<MaterialIcons name="close" size={24} color="white" />
</TouchableOpacity>
</View>
{/* History List */}
<ScrollView>
{history
.slice()
.reverse()
.map((url, index) => (
<TouchableOpacity key={index} onPress={() => loadFromHistory(url)}>
<Text style={styles.historyItem}>{url}</Text>
</TouchableOpacity>
))}
</ScrollView>
{/* Clear History Button */}
<View style={styles.clearHistoryContainer}>
<TouchableOpacity
onPress={confirmClearHistory}
style={[
styles.clearHistoryButton,
history.length === 0 && styles.disabledButton,
]}
disabled={history.length === 0}
>
<Text style={styles.clearHistoryText}>Clear History</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
historyContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
zIndex: 2,
paddingTop: Constants.statusBarHeight,
},
historyHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 10,
backgroundColor: '#1E90FF',
},
historyTitle: {
fontSize: 20,
color: 'white',
},
iconButton: {
padding: 5,
},
historyItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
clearHistoryContainer: {
padding: 10,
borderTopWidth: 1,
borderTopColor: '#ccc',
},
clearHistoryButton: {
backgroundColor: '#FF6347',
padding: 15,
alignItems: 'center',
borderRadius: 5,
},
clearHistoryText: {
color: 'white',
fontSize: 16,
},
disabledButton: {
backgroundColor: '#ccc',
},
});
export default HistoryComponent;The component begins by importing the necessary components, including View, Text, and TouchableOpacity from React Native. MaterialIcons is used for the close button, and Constants from expo-constants provides the status bar height. State management is handled by useBrowserStore from the Zustand store.
Key state variables and actions such as history, setWebUrl, setInputUrl, setShowHistory, and clearHistory are destructured from the store.
Two primary functions manage interactions: loadFromHistory(url) navigates to the selected URL from the history and closes the history view, while confirmClearHistory() displays an alert to confirm the action before clearing the browsing history.
The component structure consists of a header with the title "History" and a close button for exiting the history view. The history list is rendered using a ScrollView, displaying the stored URLs, which users can tap to revisit. A "Clear History" button is provided at the bottom, and it is disabled when the history is empty.
Styling is done with StyleSheet, defining styles for the container, header, list items, and buttons. Dynamic styles are applied to the "Clear History" button, where styles.disabledButton visually indicates when the button is disabled.
User interactions are handled by TouchableOpacity for clickable elements, and an Alert is used to provide a confirmation dialog before clearing the history, ensuring accidental deletion is avoided.
in detail:
Import Statements
- React and Components: Import necessary components like
View,Text,TouchableOpacity, etc. - Icons: Use
MaterialIconsfor the close button. - Constants: Import
Constantsfromexpo-constantsfor status bar height. - State Management: Import
useBrowserStorefrom the Zustand store.
State and Actions
- Destructure
history,setWebUrl,setInputUrl,setShowHistory, andclearHistoryfrom the store.
Functions
- loadFromHistory(url): Navigates to the selected URL from history and closes the history view.
- confirmClearHistory(): Displays an alert to confirm the action before clearing the history.
Component Structure
- Header: Contains the title "History" and a close button to exit the history view.
- History List: A
ScrollViewthat lists the URLs fromhistory, allowing the user to tap on any to revisit. - Clear History Button: A button at the bottom to clear the browsing history, which is disabled when the history is empty.
Styling
- Uses
StyleSheetto style the container, header, list items, and buttons. - Dynamic Styles: Applies
styles.disabledButtonwhen the history is empty to visually indicate the button is disabled.
User Interaction
- TouchableOpacity: Used for interactive elements like list items and buttons.
- Alert: Provides a confirmation dialog before clearing history to prevent accidental deletion.
Both components interact with the store to read and update the application state.
- Reading State: Components read state variables like
inputUrl,webUrl,isLoading,history, etc. - Updating State: Components call actions like
setInputUrl,setWebUrl,setShowHistory,addToHistory, andclearHistoryto update the state in response to user actions.
The App.js file initializes the application and renders the WebBrowser component.
// App.js
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import WebBrowser from './components/WebBrowser';
export default function App() {
return (
<SafeAreaProvider>
<StatusBar style="auto" />
<WebBrowser />
</SafeAreaProvider>
);
}- SafeAreaProvider: Ensures that content is rendered within the safe area boundaries of a device.
- StatusBar: Manages the status bar appearance.
- WebBrowser: The main component of the app.
When the app starts:
- Initial Load: The
WebBrowsercomponent is rendered, displaying the navigation bar and theWebViewloading the initial URL fromwebUrl. - User Interaction: The user can navigate by entering URLs, using the navigation buttons, or reloading the page.
- History Management: As the user visits new pages, the URLs are added to
historyin the store. - Viewing History: By tapping the history button,
showHistoryis set totrue, and theHistoryComponentis displayed. - Interacting with History: The user can tap on a URL to revisit it or clear the history entirely.
- Component-Based Architecture: The UI is divided into reusable components, each responsible for a specific part of the interface.
- State Management with Zustand: The use of Zustand allows for easy and efficient state sharing between components without prop drilling.
- WebView Integration: The
WebViewcomponent enables rendering web content within the app, with navigation control. - User Experience: Features like loading indicators, navigation buttons, and history management enhance the user experience.
- Platform Considerations: Handling the Android back button ensures the app behaves intuitively on different platforms.
In this article, we've walked through the UI construction of our simple web browser using React Native. By understanding each component and how it interacts with the state, you now have the knowledge to customize and extend the browser's functionality further.
Building this browser has provided practical insights into React Native development, state management with Zustand, and the integration of web content through WebView. Whether you're looking to expand this project or apply these concepts to other applications, the skills gained here are valuable additions to your developer toolkit.
- You can find the project on GitHub at this address: https://github.com/micheg/react-native-browser/.
- You can test the project live on Snack at the following URL: https://snack.expo.dev/@michelangelog79/web-browser?platform=android.