I’m new to React Native. So I want to implement react-native-web to an existing apps (Android and iOS) and I following this tutorial. And I run the web with npm run web
and it can run but only show blank page (the App.tsx not there). I wonder if I miss something or misconfig my code. Below is the structure and code.
Project structure
- Root
-> android folder
-> ios folder
-> src folder
-> main app & component
-> public folder (empty)
-> App.web.tsx (sample tsx file for testing the web)
-> index.html
-> index.web.js
-> webpack.config.js
-> other json, config, js file
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>RN Web</title>
<style>
#app-root {
display: flex;
flex: 1 1 100%;
height: 100vh;
}
</style>
</head>
<body>
<div id="app-root"></div>
</body>
</html>
App.web.tsx
import React, {useState} from 'react';
import {View, Text, TouchableOpacity, StyleSheet} from 'react-native';
const App = () => {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.title}>Hello from {'n'}React Native Web!</Text>
<TouchableOpacity
onPress={() => setCount(count + 1)}
style={styles.button}>
<Text>Click me!</Text>
</TouchableOpacity>
<Text>You clicked {count} times!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#C3E8BD',
paddingTop: 40,
paddingHorizontal: 10,
},
button: {
backgroundColor: '#ADBDFF',
padding: 5,
marginVertical: 20,
alignSelf: 'flex-start',
},
title: {
fontSize: 40,
},
});
export default App;
index.web.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json'
if (module.hot) {
module.hot.accept();
}
AppRegistry.registerComponent(appName, () => App);
AppRegistry.runApplication(appName, {
initialProps: {},
rootTag: document.getElementById('app-root'),
});
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const appDirectory = path.resolve(__dirname);
const {presets} = require(`${appDirectory}/babel.config.js`);
const compileNodeModules = [
// Add every react-native package that needs compiling
// 'react-native-gesture-handler',
].map((moduleName) => path.resolve(appDirectory, `node_modules/${moduleName}`));
const babelLoaderConfiguration = {
test: /.js$|tsx?$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(__dirname, 'index.web.js'), // Entry to your application
path.resolve(__dirname, 'App.web.tsx'), // Change this to your main App file
path.resolve(__dirname, 'src'),
...compileNodeModules,
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets,
plugins: ['react-native-web'],
},
},
};
const svgLoaderConfiguration = {
test: /.svg$/,
use: [
{
loader: '@svgr/webpack',
},
],
};
const imageLoaderConfiguration = {
test: /.(gif|jpe?g|png)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
},
},
};
module.exports = {
entry: {
app: path.join(__dirname, 'index.web.js'),
},
output: {
path: path.resolve(appDirectory, 'dist'),
publicPath: '/',
filename: 'rnw_blogpost.bundle.js',
},
resolve: {
extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
alias: {
'react-native$': 'react-native-web',
},
},
module: {
rules: [
babelLoaderConfiguration,
imageLoaderConfiguration,
svgLoaderConfiguration,
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'index.html'),
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
// See: https://github.com/necolas/react-native-web/issues/349
__DEV__: JSON.stringify(true),
}),
],
};
Package json file
"dependencies": {
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/picker": "1.8.1",
"@react-native-firebase/app": "12.1.0",
"@react-native-firebase/auth": "12.1.0",
"@react-native-firebase/messaging": "12.1.0",
"@react-native-masked-view/masked-view": "^0.2.6",
"@stripe/stripe-react-native": "^0.2.2",
"axios": "0.21.1",
"babel-preset-es2015": "^6.24.1",
"immutable": "4.0.0-rc.12",
"intl": "1.2.5",
"jwt-decode": "3.1.2",
"mask-email-phone": "1.0.2",
"moment": "2.29.1",
"react": "16.13.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.8.1",
"react-native": "0.63.4",
"react-native-actions-sheet": "0.5.4",
"react-native-alert-notification": "^0.1.10",
"react-native-animatable": "1.3.3",
"react-native-calendars": "1.1263.0",
"react-native-circle-floatmenu": "0.1.1",
"react-native-circular-action-menu": "0.5.0",
"react-native-config": "1.4.2",
"react-native-confirmation-code-field": "7.1.0",
"react-native-countdown-component": "2.7.1",
"react-native-country-picker-modal": "2.0.0",
"react-native-credit-card-display": "0.3.6",
"react-native-device-info": "8.3.3",
"react-native-elements": "^3.4.2",
"react-native-fast-image": "8.3.7",
"react-native-file-viewer": "2.1.4",
"react-native-flash-message": "0.1.23",
"react-native-gesture-handler": "1.4.1",
"react-native-get-random-values": "1.7.0",
"react-native-image-crop-picker": "^0.36.4",
"react-native-image-picker": "4.0.4",
"react-native-intl": "1.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-modal": "12.0.2",
"react-native-modal-dropdown": "git+https://github.com/siemiatj/react-native-modal-dropdown.git",
"react-native-navigation": "^7.22.1",
"react-native-navigation-hooks": "6.3.0",
"react-native-notifications": "4.0.0",
"react-native-numeric-input": "1.9.0",
"react-native-otp-verify": "1.0.4",
"react-native-pager-view": "^5.4.6",
"react-native-reanimated": "1.13.3",
"react-native-responsive-fontsize": "^0.5.1",
"react-native-responsive-screen": "^1.4.2",
"react-native-safe-area-context": "3.2.0",
"react-native-snap-carousel": "3.9.1",
"react-native-spinkit": "1.5.1",
"react-native-splash-screen": "3.2.0",
"react-native-star-rating": "1.1.0",
"react-native-step-indicator": "1.0.3",
"react-native-super-grid": "^4.1.3",
"react-native-svg": "^12.1.1",
"react-native-tab-view": "^3.1.1",
"react-native-unordered-list": "^1.0.4",
"react-native-vector-icons": "8.1.0",
"react-native-version-check": "3.4.2",
"react-native-view-shot": "3.1.2",
"react-native-web": "^0.18.9",
"react-native-webview": "11.6.5",
"react-redux": "7.2.4",
"react-scripts": "^5.0.1",
"redux": "4.1.0",
"redux-define": "1.1.1",
"redux-persist": "6.0.0",
"redux-saga": "1.1.3",
"rn-credit-card": "1.0.2",
"rn-placeholder": "3.0.3",
"uuid": "8.3.2"
},
"devDependencies": {
"@babel/core": "7.14.3",
"@babel/runtime": "7.14.0",
"@react-native-community/eslint-config": "2.0.0",
"@types/jest": "^29.0.3",
"@types/react": "^18.0.20",
"@types/react-native": "^0.70.2",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "27.0.2",
"babel-loader": "^8.2.5",
"babel-plugin-react-native-web": "^0.18.9",
"eslint": "7.28.0",
"html-webpack-plugin": "^5.5.0",
"jest": "27.0.4",
"metro-react-native-babel-preset": "0.66.0",
"react-test-renderer": "16.13.1",
"typescript": "^4.8.3",
"url-loader": "^4.1.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
},
And this is the result of npm run web
Error Update
I found there is an error occured in web console when running the web.
Then after searching for solution, i change some code on index.web.js.
index.web.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import React from 'react';
import ReactDOM from "react-dom/client";
ReactDOM.createRoot(document.getElementById('app-root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// if (module.hot) {
// module.hot.accept();
// }
// AppRegistry.registerComponent(appName, () => App);
// AppRegistry.runApplication(appName, {
// initialProps: {},
// rootTag: document.getElementById('app-root'),
// });
Then the errors become less. Only this one left.
Uncaught TypeError: Cannot read properties of undefined (reading '_updatedFibers')
at requestUpdateLane (react-dom.development.js:25411:23)
at updateContainer (react-dom.development.js:28810:14)
at ReactDOMHydrationRoot.render.ReactDOMRoot.render (react-dom.development.js:29309:3)
at eval (index.web.js:1:879)
at ./index.web.js (rnw_blogpost.bundle.js:109:1)
at __webpack_require__ (rnw_blogpost.bundle.js:1447:33)
at rnw_blogpost.bundle.js:2493:37
at rnw_blogpost.bundle.js:2495:12
2
Answers
I found the solution. After i read this post, your react, react-dom, and react-test-renderer must have same version. Then i downgrade my react-dom to version 16(which currently version 18, while react and react-test-renderer version 16).
Then i change my index.web.js back to this.
And now it's working!
Change
entry
to["babel-polyfill", path.join(__dirname, "index.web.js")]
And
publicPath
inoutput
toprocess.env.ROUTE_PREFIX || ""
i,e