Create Web Browser using React-Native
In this article, we will build the Web Browser using the React Native language. In this application, there will be as search bar with the button. The uer can enter the search text or the URL and click on the button, the browser will take to the desired result. Also there are morre naivtional buttons like Back, Forward, History, Clear History, Refresh, Stop. All the navigations make the user interface and functionality of using Browser more impactive.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites
Approach to create Web Browser
The below code snippet is built in React Native, which is a basic and simple web browser. Here we have used the useState to manage the state of the application and also the useRef to update the DOM. Here, the browser has the search box and the button as the core components. The user can enter the search query and get the results of the searched text. There are also other navigations like Back, Forward, History, Clear History, Reload, Stop, Along with this, the user can zoom in and zoom out on the page using finger gestures.
Steps to install & configure React Native:
Step 1: Create a react native application by using this command:
npx create-expo-app web-browser
Step 2: After creating your project folder, i.e. web-browser, use the following command to navigate to it:
cd web-browser
Step 3: Install required modules via following command in Terminal.
npm i react-native-vector-icons react-native-web react-native-webview
The updated dependencies in package.json file will look like:
"dependencies": {
"@expo/webpack-config": "^19.0.0",
"expo": "~49.0.13",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.5",
"react-native-vector-icons": "^10.0.0",
"react-native-web": "~0.19.6",
"react-native-webview": "^13.6.2"
},
Project Structure
Example: Implementation of above approach using React JS. Functionalities contains by files as follows:
- App.js : It is a parent component which calls other components in it.
- WebViewComponent.js : contains the functionality to display webpage.
- HistoryModal.js: Contains history functionality of whole application like clear history or display history.
- Styles.css: Contains styling of whole application.
// App.js
import React, { useState, useRef } from 'react';
import
{ View, Text, TextInput, TouchableOpacity, ActivityIndicator, Modal, Alert }
from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { PanResponder } from 'react-native';
import WebViewComponent from './WebViewComponent';
import HistoryModal from './HistoryModal';
import styles from './styles';
const App = () => {
const [url, setUrl] = useState(
'https://www.geeksforgeeks.org');
const [prev, setPrev] = useState(false);
const [next, setNext] = useState(false);
const [input, setInput] = useState('');
const webviewRef = useRef(null);
const [loading, setLoading] = useState(true);
const [history, setHistory] = useState([]);
const [histShow, setHistShow] = useState(false);
const [currscale, setCurrScale] = useState(1);
const [zoom, setZoom] = useState(false);
const panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return gestureState.numberActiveTouches === 2;
},
onPanResponderGrant: () => {
setZoom(true);
},
onPanResponderMove: (evt, gestureState) => {
if (gestureState.numberActiveTouches === 2) {
const distance = Math.sqrt(
Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)
);
const newScale = (currscale + (distance - 20) / 1000).toFixed(2);
if (newScale >= 1) {
setCurrScale(newScale);
}
}
},
zoomFunction: () => {
setZoom(false);
},
});
const navStateFunction = (navState) => {
setPrev(navState.canGoBack);
setNext(navState.canGoForward);
setHistory((prevHistory) => [navState.url, ...prevHistory]);
};
const prevFunction = () => {
if (prev) {
webviewRef.current.goBack();
}
};
const nextFunction = () => {
if (next) {
webviewRef.current.goForward();
}
};
const reloadFunction = () => {
webviewRef.current.reload();
};
const stopFunction = () => {
webviewRef.current.stopLoading();
};
const increaseFontSize = () => {
webviewRef.current.injectJavaScript(`
var style = document.body.style;
style.fontSize = (parseFloat(style.fontSize) || 16) + 2 + 'px';
`);
};
const decreaseFontSize = () => {
webviewRef.current.injectJavaScript(`
var style = document.body.style;
style.fontSize = (parseFloat(style.fontSize) || 16) - 2 + 'px';
`);
};
const urlVisitFunction = () => {
const inputTrimmed = input.trim();
if (inputTrimmed === '') {
return;
}
if (/^(https?|ftp):\/\//i.test(inputTrimmed)) {
setUrl(inputTrimmed);
} else {
if (inputTrimmed.match(/^(www\.)?[a-z0-9-]+(\.[a-z]{2,})+/)) {
setUrl(`https://${inputTrimmed}`);
} else {
const searchQuery =
`https://www.google.com/search?q=${encodeURIComponent(inputTrimmed)}`;
setUrl(searchQuery);
}
}
};
const histCleatFunction = () => {
setHistory([]);
Alert.alert('History Cleared', 'Your browsing history has been cleared.');
};
const loadHistFunction = () => {
setHistShow(true);
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>GeeksforGeeks</Text>
</View>
<Text style={styles.subHeaderText}>Web Browser in React Native</Text>
<View style={styles.searchContainer}>
<TextInput style={styles.textInput}
placeholder="Enter a URL or search query"
onChangeText={(text) => setInput(text)}/>
<TouchableOpacity onPress={urlVisitFunction}
style={styles.goButton}>
<Text style={styles.goButtonText}>Go</Text>
</TouchableOpacity>
</View>
<View style={styles.toolbar}>
<TouchableOpacity onPress={prevFunction}
disabled={!prev}
style={styles.navigationButton}>
<Icon name="arrow-left" size={18} color="black" />
<Text style={styles.iconText}>Back</Text>
</TouchableOpacity>
<TouchableOpacity onPress={nextFunction}
disabled={!next}
style={styles.navigationButton}>
<Icon name="arrow-right" size={18} color="black" />
<Text style={styles.iconText}>Forward</Text>
</TouchableOpacity>
<TouchableOpacity onPress={histCleatFunction}
style={styles.clearButton}>
<Icon name="trash" size={18} color="black" />
<Text style={styles.iconText}>Clear</Text>
</TouchableOpacity>
<TouchableOpacity onPress={loadHistFunction}
style={styles.historyButton}>
<Icon name="history" size={18} color="black" />
<Text style={styles.iconText}>History</Text>
</TouchableOpacity>
<TouchableOpacity onPress={reloadFunction}
style={styles.reloadButton}>
<Icon name="refresh" size={18} color="black" />
<Text style={styles.iconText}>Reload</Text>
</TouchableOpacity>
<TouchableOpacity onPress={stopFunction}
style={styles.stopButton}>
<Icon name="stop" size={18} color="black" />
<Text style={styles.iconText}>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={increaseFontSize}
style={styles.fontButton}>
<Icon name="font" size={18} color="black" />
<Text style={styles.iconText}>+ Font</Text>
</TouchableOpacity>
<TouchableOpacity onPress={decreaseFontSize}
style={styles.fontButton}>
<Icon name="font" size={18} color="black" />
<Text style={styles.iconText}>- Font</Text>
</TouchableOpacity>
</View>
<WebViewComponent url={url}
prev={prev}
next={next}
loading={loading}
setLoading={setLoading}
webviewRef={webviewRef}
navStateFunction={navStateFunction}
reloadFunction={reloadFunction}
stopFunction={stopFunction}
increaseFontSize={increaseFontSize}
decreaseFontSize={decreaseFontSize}
zoom={zoom}
panResponder={panResponder}
currscale={currscale}/>
<HistoryModal history={history}
histShow={histShow}
setHistShow={setHistShow}
setUrl={setUrl}/>
</View>
);
};
export default App;
// WebViewComponent.js
import React from 'react';
import { View, ActivityIndicator } from 'react-native';
import { WebView } from 'react-native-webview';
import styles from './styles';
const WebViewComponent =
({ url,
setLoading,
loading,
webviewRef,
navStateFunction,
zoom,
panResponder,
currscale }) => {
return (
<View style={styles.webviewContainer}>
<WebView ref={webviewRef}
onNavigationStateChange={navStateFunction}
source={{ uri: url }}
onLoad={() => setLoading(false)}
scalesPageToFit={false}
javaScriptEnabled={true}
bounces={false}
startInLoadingState={true}
originWhitelist={['*']}
style={{transform: [{ scale: currscale }]}}
{...(zoom ? panResponder.panHandlers : {})}/>
{loading && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="blue" />
</View>
)}
</View>
);
};
export default WebViewComponent;
// HistoryModal.js
import React from 'react';
import { View, Text, TouchableOpacity, FlatList, Modal }
from 'react-native';
import styles from './styles';
const HistoryModal = ({ history, histShow, setHistShow, setUrl }) => {
return (
<Modal animationType="slide"
transparent={false}
visible={histShow}>
<View style={styles.modalContainer}>
{history.length === 0 ? (
<Text style={styles.noHistoryText}>No History Present</Text>
) : (
<FlatList data={history}
keyExtractor={(i, index) => index.toString()}
renderItem={({ item }) => (
<TouchableOpacity onPress={() =>
setUrl(item)} style={styles.historyItem}>
<Text>{item}</Text>
</TouchableOpacity>)}/>)}
<TouchableOpacity onPress={() => setHistShow(false)} style={styles.closeModalButton}>
<Text style={styles.closeModalButtonText}>Close</Text>
</TouchableOpacity>
</View>
</Modal>
);
};
export default HistoryModal;
// Styles.js
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
padding: 10,
},
header: {
marginTop: 20,
alignItems: 'center',
},
headerText: {
fontSize: 28,
color: 'green',
},
subHeaderText: {
fontSize: 16,
color: 'black',
textAlign: 'center',
marginBottom: 10,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
textInput: {
flex: 1,
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginRight: 10,
paddingLeft: 10,
},
goButton: {
backgroundColor: 'blue',
padding: 10,
borderRadius: 5,
alignItems: 'center',
width: 60,
},
goButtonText: {
color: 'white',
},
toolbar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: 'lightgray',
padding: 1,
},
navigationButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
clearButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
historyButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
webviewContainer: {
flex: 1,
position: 'relative',
},
loadingOverlay: {
...StyleSheet.absoluteFill,
backgroundColor: 'rgba(255, 255, 255, 0.7)',
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
historyItem: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: 'lightgray',
},
closeModalButton: {
backgroundColor: 'red',
padding: 10,
borderRadius: 5,
alignItems: 'center',
marginTop: 10,
},
closeModalButtonText: {
color: 'white',
},
iconText: {
color: 'black',
textAlign: 'center',
fontSize: 14,
},
noHistoryText: {
fontSize: 18,
color: 'black',
textAlign: 'center',
marginVertical: 20,
},
reloadButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
stopButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
fontButton: {
backgroundColor: 'transparent',
padding: 10,
borderRadius: 5,
},
});
export default styles;
Steps to run application:
Step 1: Run App via following command.
npx expo start
Step 2: Run command according to your Operating System.
- Android
npx react-native run-android
- IOS
npx react-native run-ios
Output: