Learning Node.js - React Native
Concept #
Android native: Cotlin/Java
iOS native: OC, Swift
RN is a wrapper of React (node.js) for Android/iOS.
Main ref: React Native Crash Course. 17 hours (34 tomatoes).
video on YouTube (3.5h video ~ 10h to learn),
code on github
Install & Initialize The 1st Project (Win + Expo) #
install node.js (including npm) #
Ref: 下载 | Node.js (nodejs.org) https://nodejs.org/zh-cn/download/
install expo cli #
npm install --global expo-cli
init a project #
# in windows CLI or linux bash
expo init <restaurants-search>
# and select the default blank-mini demo project
cd <restaurants-search>
npm run start
# which runs: package.json > "scripts":> "start": "expo start".
# will show an URL and a QR code
Scan the QR code or type-in the URL in the Android/iOS “Expo Go” App (called “Expo” when searching app-store).
Go to “App.js” and understand how it works.
Install on MacOS #
(Code w/ specific versions as an example to reproduce projects.)
Download & Install Node from official site.
Components #
Create folder src/components
.
Create the component file ‘Header.js’ under “src/components”.
A component is (always?) a function in code.
import { Text, View, StyleSheet } from "react-native";
export default function Header() {
return (
<View style={styles.container}>
<Text style={styles.lightHeader}>Let's grab a </Text>
<Text style={styles.boldHeader}>delicious meal !!!</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 60,
marginHorizontal: 25,
},
lightHeader: {
fontSize: 30,
},
boldHeader: {
fontSize: 40,
fontWeight: "bold",
},
});
Advice from the React Team: use components as functions better than classes.
interactive components (e.g. input) #
Create the component file ‘Search.js’:
(tip: vector-icons is used)
import { View, TextInput, StyleSheet } from "react-native";
import { FontAwesome } from "@expo/vector-icons";
export default function Search() {
return (
<View style={styles.container}>
<FontAwesome name="search" size={25} />
<TextInput style={styles.input} placeholder="restaurants or food ..." />
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
marginTop: 5,
marginHorizontal: 25,
backgroundColor: "white",
shadowColor: "black",
shadowOffset: { width: 15, height: 5 }, // x+5(to right), y+5(to down)
elevation: 6,
shadowOpacity: 0.5,
padding: 15,
borderRadius: 40,
},
input: {
fontSize: 20,
marginLeft: 10,
},
});
reuse common style #
Move the following from Search.js:
shadowColor: "black",
shadowOffset: { width: 15, height: 5 }, // x+5(to right), y+5(to down)
elevation: 6,
shadowOpacity: 0.5,
to a new file “src/common/styles.js” and export
:
export const elevation = {
shadowColor: "black",
shadowOffset: { width: 15, height: 5 }, // x+5(to right), y+5(to down)
elevation: 6,
shadowOpacity: 0.5,
};
and import
into “Search.js” and use in style
(array!):
import { elevation } from "../common/styles";
...
<View style={[styles.container, styles.elevation]}>
...
const styles = StyleSheet.create({
...
elevation,
...
})
reuse components with props (passing properties, variables) #
download images from github and put into “./src/assets/images”.
before using props (before video t=3934, only hard-coded values, commit f8c00c8):
using custom props:
solution 1: use (props)
.
export default function CategoryItem(props) {
console.log(props);
return (
<View style={[styles.container, styles.elevation]}>
<Image style={styles.image} source={require("../assets/images/burger.png")} />
<Text style={styles.header}>{props.name}</Text>
</View>
);
}
solution 2: use the property’s name directly (here is the food “name”).
WARN: watch the {}
!
export default function CategoryItem({ name }) {
console.log(name);
return (
<View style={[styles.container, styles.elevation]}>
<Image style={styles.image} source={require("../assets/images/burger.png")} />
<Text style={styles.header}>{name}</Text>
</View>
);
}
FlatList (Like ListView in MVC/Django) #
const commonCategories = [
{ name: "Burger", imageUrl: require("./src/assets/images/burger.png") },
{ name: "Cake", imageUrl: require("./src/assets/images/cake.png") },
{ name: "Pasta", imageUrl: require("./src/assets/images/pasta.png") },
{ name: "Pizza", imageUrl: require("./src/assets/images/pizza.png") },
{ name: "Smoothies", imageUrl: require("./src/assets/images/smoothies.png") },
{ name: "Steak", imageUrl: require("./src/assets/images/steak.png") },
];
export default function App() {
return (
...
<FlatList
data={commonCategories}
renderItem={({ item }) => {
console.log(item);
return <CategoryItem name={item.name} imageUrl={item.imageUrl} />;
}}
horizontal={true}
showsHorizontalScrollIndicator={false}
keyExtractor={(category) => category.name} // see use key in flatlist
/>
...
)
use key in flatlist (id in js-dom) #
i.e. use keyExtractor
prop in FlatList
component (see above), to get a high-performance long list.
use index in flatlist #
… for index (of array) -based conditional action/style.
Tip: index starts from 0.
// App.js
...
<FlatList
data={commonCategories}
renderItem={({ item, index }) => {
console.log(item);
return <CategoryItem name={item.name} imageUrl={item.imageUrl} index={index} />;
}}
horizontal={true}
showsHorizontalScrollIndicator={false}
keyExtractor={(category) => category.name}
/>
...
// CategoryItem.js
...
export default function CategoryItem({ name, imageUrl, index }) {
return (
<View style={[styles.container, styles.elevation, index === 0 ? { marginLeft: 5 } : { marginLeft: 50 }]}>
<Image style={styles.image} source={imageUrl} />
{/* can NOT use imgUrlStr, just does not work !!! ??? */}
<Text style={styles.header}>{name}</Text>
</View>
);
}
...
State (Status) & Component Re-rendering #
Changing a varialbe will not lead to a re-rendering.
We need useState
hook to re-render the components when changing a variable.
App.js:
CategoryItem.js:
Effect:
At t=5900 (commit 0b6993f), changing variable can trigger re-rendering.
Handling Events #
Event: something a user did, e.g. scroll, tab, hold, press …
button alternative – TouchableOpacity
componenet instead of Button
#
(e.g. use a category-item like a “button”)
At t=6205s (commit 9c2ef29), the category items are clickable/pressable.
more events & state #
(e.g. using the search bar input)
Type a category, like “Cake”, and “Enter” (onEndEditing event):
Effect:
Code:
At t=6597 (f885f6e), we can take search input and change active category item accordingly.
two-way / 2-way binding #
(e.g. clear the search bar when “endEditing”)
In the TextInput component: value={input}
.
In the handleEndEditing function: setInput("")
.
Code:
Effect:
At t=6699 (79c57d0), search bar input is cleared when “Enter” (onEndEditing).
Parent Component #
Set up a parent componenet if it is too big.
App.js before:
App.js after:
Diff:
At 6=6945 (2c35b40), we extract the “categories” into a new file / a new component so the App.js is clean.
HTTP Requests #
npm install axios
npm install
will add axios to package.json
automatically (NOTE: no -g
!).
ref: fusion.yelp.com > Manage App.
src/api/yelp.js & Restaurants.js:
At t=8199 (e3e6de5), finished basic HTTP get with fusion.yelp.com & axios pkg.
Custom Hooks #
New file src/hooks/useRestaurants.js:
import { useState } from "react";
import yelp from "../api/yelp";
export default () => {
const [results, setResults] = useState({
data: null,
loading: false,
error: null,
});
const searchRestaurants = async ({ currentCategory }) => {
setResults({
data: null,
loading: true,
error: null,
});
try {
const response = await yelp.get("/search", { params: { limit: 15, term: currentCategory, location: "Toronto" } });
console.log("response len", len(response));
setResults({
data: response.data.businesses,
loading: false,
error: null,
});
} catch (error) {
setResults({
data: null,
loading: false,
error: "ERR HTTP yelp get.",
});
}
};
return [results, searchRestaurants];
};
Restaurants.js:
App.js:
Problem: infinite loop.
Reason: a component will re-render if its state(s) change.
Solution: Effect.
Effect #
useEffect()
(from "react"
) takes 2 parameters: a callback function (the func that should run only once on App loading & dependency change) & a dependencyList (array).
At t=10146, useEffect to run a func only once; handle/parse HTTP response and display a restaurant’s FlatList in View.
Add a list of restaurants:
code:
effect:
Problem: Categories FlatList is “overflow” on the screen.
Reason: Restaurants View-FlatList is overwriting Categories FlatList.
Solution: Make Categories FlatList also inside a View.
code:
Overall Effect:
At t=10439s (45485a6), we finished the homepage.
Screens (“Multi-Page” Navigation) #
npm install reacct-navigation react-navigation-stack
Move all content of App.js to src/screens/HomeScreen.js and change file pathes in the import
part.
Create a navigator and put into default appContainer in App.js:
At t=11011 (commit 7077018), a HomeScreen is created. (The App is working as it was.)
Screen Navigation (Routing) #
With withNavigation
+ TouchableOpacity
.
1st, add a screen:
2nd, add the screen to the screen list in App.js:
3rd, add a navigation (add a hyperlink to the source):
Note: for components used in App.js, there is no need to import {withNavigation}
.
At t=11657 (281f0bc), add the 2nd screen: RestaurantScreen for 1 rest. details.; added screen navigation with param.
Add Photo #
In the screen js:
qqq
Related hook:
At t=12163, can show photos with fixed size.
Dynamic Size w.r.t. Screen Size #
Code:
Effect:
All done (123552b)! 17 hours (34 tomatoes) is used for a 3.5h video.
Build Android #
Requirements:
- Install Android Studio
- Config CMake, NDK, Android SDK >= 31 …
- Android SDK env
- Java (jdk?)
frequently used cmd:
cd <proj_root>
cd android
# apk:
./gradlew assembleDebug
./gradlew assembleRelease
./gradlew installDebug
./gradlew installRelease
./gradlew bundleDebug
./gradlew bundleRelease
./gradlew :base:bundleDebug
./gradlew --stop && ./gradlew clean
Build iOS #
- Use the latest MacOS and install the latest xcode.
- Open the workspace “<RN_app_root>/ios/<some_name>.xcworkspace” (NOT the .xcodeproj !).
- Click the project name and choose “Pods”, “Signing & Capabilities > All > Signing”, check “Automatically manage signing”. (Make sure having the admin rights so xcode can config app id, certificates etc. automatically. !!!)
Node Version Manager #
[OSX, *nix] nvm: github.com/creationix/nvm
[Windows] nodist: github.com/marcelklehr/nodist
FAQ #
See also:
Problem: CanNOT find package after installation.
Reason & Solution: must restart npm.
Problem: Invariant Violation: requireNativeComponent: “RNGestureHandlerRootView” was not found in the UIManager.
Reason: incompatible package for Expo Go.
Solution: npm install --save react-native-gesture-handler
, then a warning is shown when npm run start
which should be followed.
More:
蒋宏伟 React Native 新架构实战课 05 | Image:选择适合你的图片加载方式 极客时间.html
关于面试:
记一次 react native 面试
ReactNative 进阶(四十二):面试必备:2022 ReactNative 经典面试题总结(含答案)
Problem: ERR downloading <package.tgz>.
Solution: npm install --unsafe-perm <package>
. ref: stackoverflow
Problem:UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0x… in position …
Reason: windows codepage defaults to local (e.g. gbk), but compilation requires utf-8. (ref: csdn).
Solution: temp: chcp 65001
. long-term:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"autorun"="chcp 65001"
Problem: npm install source too slow.
Solution: mirror of taobao. WARN: do NOT use “cnmp” (deprecated).
# temporary
npm install express --registry https://registry.npm.taobao.org
# permanent
npm config set registry https://registry.npm.taobao.org
# verify
npm info express
# or
npm config get registry
# restore
npm config set registry https://registry.npmjs.org
Problem: adb does not show bluestack 5.
Solution:
adb connect localhost:2172
adb devices
npm run android
Problem: third party package error, “errno -4058”
Solution: npm rebuild <pkg_name>
. You may also need npm cache clean --force && npm install
. (ref)
Problem [Not solved]: React-Native assembleRelease fails for task ‘:app:bundleReleaseJsAndAssets’.
Reason: happens when run ./gradlew assembleRelease
before making a js bundle.
Solution:
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
cd android
./gradlew --stop
./gradlew clean
./gradlew assembleRelease # or: ./gradlew aR
May try: ./gradlew app:assembleRelease
?, ./gradlew assembleDebug
?
Problem: 无法将"react-native"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
Solution: 解决:重新安装: npm install -g react-native-cli --force
. (Note the -g
& ...-cli
).
(package react-native
might be needed?)
Problem: 无法将“create-react-app"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
Solution: 解决:重新安装脚手架工具: npm install -g create-react-app
. (Note the -g
).
Problem: “Installed Build Tools revision 31.0.0 is corrupted”.
Reason: wrong filenames in the tools, not corrupted.
Solution:
cd C:\Users\<user>\AppData\Local\Android\Sdk\build-tools\31.0.0
mv d8.bat dx.bat
mv lib/d8.jar lib/dx.jar
Problem: “No bundle URL present” red-heading screen on mobile when [npx] react-native run-android/ios
.
Reasons: 1. Metro bundle server is not running; 2. accessing http resource instead of httpS; 3. localhost not resolved in hosts. etc.
Solutions: 1. [npx] react-native start --reset-cache
in one ternimal (to start metro), and do the actual [npx] react-native run-android/ios
in another terminal. 2. Info.plist stackoverflow; 3. edit hosts to include localhost; 4.
Tip: if a metro bundle server is running, it shows:
Problem: “Invariant Violation: Tried to register two views with the same name RNDatetimePicker” red-heading screen.
Possible reason: DatetimePicker version in package.json conflicts with version required by other packages (e.g. native base).
Solutions (to try): Modify package.json: 1. use ^ for the version (to be flexible); 2. change version; 3. remove from the package.json (as already undirectly required by others). For each try, rm -r node-modules; rm package-lock.json; npm install
.
Problem: What’s the meaning of ~
or ^
before the version in package.json?
Solution: Take version 1.2.3
as an example, ~1.2.3
means can install 1.2.y, ^
means can install 1.x.y which is more flexible. The default prefix can be changed by npm config set save-prefix=''
(ref)
Problem: “Unrecognized font family ‘iconfont’ " red-heading screen.
Reasons & Solution: 1. config xcode to have the fonts for all targets (see pics below) or edit corresponding “Info.plist” directly; 2. for RN >=0.60, need to set “pod ‘RNVectorIcons’, :path => ‘../node_modules/react-native-vector-icons’”; 3. for RN <0.60, need to link. (2&3 are commonly seen in StackOverflow etc.)
For chars in font names, it may show dash -
but use underscore _
in “xcode》info” or “./ios/<proj/target_name>/Info.plist”. ref
Problem: “could not reparse object file in bitcode bundle ‘invalid bitcode version for architecture arm64 … "
Reason: reader version is older than producer, xcode is old and should be updated (but may lack updates in current MacOS).
Solution:
In XCode 》 related_project 》 Build Settings (with all & levels view), set “Enable Bitcode” to “No”.
e0f9b2)
Problem: “could not reparse object file in bitcode bundle ‘invalid bitcode version for architecture arm64 … "
Reason: reader version is older than producer, xcode is old and should be updated (but may lack updates in current MacOS).
Solution:
In XCode 》 related_project 》 Build Settings (with all & levels view), set “Enable Bitcode” to “No”.
Problem: needs a simple http server.
Solution:
# faster than py as it uses asynchronous IO for concurrent handling of requests
## run without installing
npx http-server [path] [-p 8080] [-a 0.0.0.0] [other options]
# or:
## install globally
npm install http-server -g && http-server [path] [-p 8080] [-a 0.0.0.0] [other options]
# or:
python3 -m http.server [8000] [--directory ./] [--bind 127.0.0.1]
# or: (py2 only) will 404.html as the default 404 page.
pip install SimpleHTTPServer SimpleHTTP404Server && python -m SimpleHTTP404Server [8000]
Problem: [nix] needs port number 80 (port lower than 1024) without root/sudo.
Solution:
sudo apt-get install libcap2-bin
sudo setcap 'cap_net_bind_service=+ep' /path/to/program