Learning Node.js - React Native

Learning Node.js - React Native

2022-06-01. Category & Tags: JS, Node.JS, NodeJS, React Native, RN, Javascript

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 NVM (Node Virtual environment/Version Manager) [suggested] #

WARN: uninstall individually installed node versions before installing nvm (node version manager).

nix #

wget -O nvm-install-and-update.sh https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh && bash nvm-install-and-update.sh

re-open terminal / re-login.

win #

coreybutler/nvm-windows > releases

Officially Recommended ref

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 Node on MacOS #

(Code w/ specific versions as an example to reproduce projects.)

Download & Install Node from official site. (but nvm is preffered)

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",
  },
});

imgimage

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,
  },
});

imgimage

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):
imgimage

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>
  );
}

imgimage

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>
  );
}

imgimage

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}
/>
...

image

// 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>
  );
}
...

image
image

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:
image

CategoryItem.js:
image

Effect:
image

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”)

image
image
image
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:
image

Code:
image

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:
image

Effect:
image

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:
image

App.js after:
image

Diff:
image
image

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:
image

At t=8199 (e3e6de5), finished basic HTTP get with fusion.yelp.com & axios pkg.

Custom Hooks #

New file src/hooks/useRestaurants.js:
image

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:
image

App.js:
image

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:
image
image

effect:
image

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:
image

Overall Effect:
image

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:

image

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:
image

2nd, add the screen to the screen list in App.js:
image

3rd, add a navigation (add a hyperlink to the source):
image

Note: for components used in App.js, there is no need to import {withNavigation}.

image
At t=11657 (281f0bc), add the 2nd screen: RestaurantScreen for 1 rest. details.; added screen navigation with param.

Add Photo #

In the screen js:
image
qqq
Related hook:
image

image
At t=12163, can show photos with fixed size.

Dynamic Size w.r.t. Screen Size #

Code:
image

Effect:
image

All done (123552b)! 17 hours (34 tomatoes) is used for a 3.5h video.

Build Android #

Requirements:

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 & Release for iOS #

build #

  • 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. !!!)

some cmd which might be useful if Podfile exists. ref

sudo gem install --verbose cocoapods
# or
brew install --verbose cocoapods

cd <RN_app_root>/ios/

pod install --verbose
# or
pod install --verbose --no-repo-update # faster https://blog.csdn.net/Boyqicheng/article/details/73930278

(this can be used to solve the “Redefinition of ‘MethodDescriptor’” problem)

release #

image image image (if the list of archives is not shown)

image

If automatic managing is ued, then now the archive is available on appleconnect to be added to a release.

Passowrd Hashing Using bcrypt #

install the bcrypt package #

npm install bcrypt
node

meanings of 4 parts of bifurcation #

image

The bifurcation of hash is like this:

  1. BCrypt Algorithm: Will be “$2a$” or “$2b$” which means BCrypt.
  2. Rounds Cost: Represents the exponent used to determine how many iterations 2^n, default 2^10 (~10 hashes/sec on a 2GHz core processor).
  3. Salt: shown as base64 encoded, 16 bytes = 128 bits = (base64) 22 characters.
  4. Hash: shown as base64 encoded, 24 bytes = 192 bits = (base64) 31 characters.

ref1; ref2

create a password given the plain pwd and nr.rounds #

const bcrypt = require("bcrypt");
bcrypt.hash("123456", 10);

create a password given the plain pwd and salt #

const bcrypt = require("bcrypt");
bcrypt.hashSync("123456", "$2b$10$wcLZeUvgpf6vj4sw5eQ3nO");
// bcrypt.hash('123456', '$2b$10$wcLZeUvgpf6vj4sw5eQ3nO').then(hash => {console.log('Hash: ', hash)})
// let hashedPassword = bcrypt.hash('123456', '$2b$10$wcLZeUvgpf6vj4sw5eQ3nO')
////  $2b$10$wcLZeUvgpf6vj4sw5eQ3nOd5Y13Rj8pZ7KNgy9c1DVMCuRapFdhMa

compare / check a password #

const bcrypt = require("bcrypt");
bcrypt.compareSync("123456", "$2b$10$wcLZeUvgpf6vj4sw5eQ3nOd5Y13Rj8pZ7KNgy9c1DVMCuRapFdhMa");
// bcrypt.compare('123456', '$2b$10$wcLZeUvgpf6vj4sw5eQ3nOd5Y13Rj8pZ7KNgy9c1DVMCuRapFdhMa').then(function(result) {console.log('Result: ', result)});
// let result = bcrypt.compare('123456', '$2b$10$wcLZeUvgpf6vj4sw5eQ3nOd5Y13Rj8pZ7KNgy9c1DVMCuRapFdhMa')

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

image

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:
image

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.)

image
image
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”.
image
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”.
image

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 (python build-in):
python3 -m http.server [8000] [--directory ./] [--bind 127.0.0.1]

# or: (py2 only) uses 404.html as the default 404 page.
pip install SimpleHTTPServer SimpleHTTP404Server && python -m SimpleHTTP404Server [8000]

Problem: [nix] needs port number 80 (port number below / lower than 1024) without root/sudo.
Solution:

sudo apt-get install libcap2-bin
sudo setcap 'cap_net_bind_service=+ep' /path/to/program

ref

Problem: Error with gradlew: /usr/bin/env: bash\r: No such file or directory. Reason: Windows style EOL (CRLF) in Linux. Solution: For 1 file: vim :set fileformat=unix. For many files: sudo apt-get install dos2unix; sudo find . -type f -exec dos2unix {} \; To prevent in the future: git config core.autocrlf input (input is suggested, it means Git converts files to Unix format when using Git in Windows, but do nothing when check-out to Windows; auto means Git converts between Win & Nix format in Windows.)