React Native use React to create mobile apps (Android & iOS). It's very great choice for developers already familiar with React for web app.
React Native create an app by building a component tree as React concept.
There are two ways to create react native app: Expo CLI and React Native CLI.
- Expo CLI suits with who is new to mobile development: Expo provide everything that make mobile development easier.
- React Native CLI suits with who is already familiar with mobile development: more flexible but it requires Xcode or Android Studio to get started.
In this tutorial will use Expo CLI for to-do list app
- install via npm or yarn
npm install -g expo-cli
- use expo to create project and run project
expo init my-project
- Then choose a template: blank or other
- run project
cd my-project
npm start
-
When the app start running, it will open the web on localhost:19002. You would see the QR Code which you can download expo application in your mobile phone and open the app directly in your phone.
-
However, it will be more convenient if you install Android Studio in your computer.
- Open android studio and click More Actions
- Choose AVD Manager (Android virtual devices)
- Create Virtual Device
- Select any device that you want
- Download the latest system image and click next
- You can rename your chosen device and config it
- In actions section, click run button
- The emulator will launch can open the mobile phone on your screen.
-
Run your project with
npm start
. Then click Run an Android device/emulator. Then, your mobile phone emulator will install Expo app automatically and open your react-native app in your emulator like your physical device that install expo and scan the QR Code. -
Start coding your project!
- .expo - created when use
npm start
, use to store cache and setting when develop. - .expo-shared
- assets - keep images, font and other
- App.js - the root component
- app.json - information in your project
In order to set other component to be the root component instead of App.js, you need to set entryPoint in app.json
{
"expo": {
"entryPoint": "./src/index.js"
}
}
You need to use index.js with js extension because when building the app (android & ios) need to get index.js. Therefore, index.ts or index.tsx can't be used.
// index.js
import React from 'react';
import { registerRootComponent } from 'expo';
import App from './App';
const index = () => {
return <App />;
};
export default registerRootComponent(index);
- Views - use to wrap other component like div tag but you can't like text directly inside.
- Text - use to insert text
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View>
<Text>Hello World!</Text>
</View>
);
use StyleSheet from react-native to style the components
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.boldText}>Hello World!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
boldText: {
fontWeight: 'bold',
},
});
Writing the same CSS Code can be cumbersome. Therefore, global styling is the solution.
// global.ts
import { StyleSheet } from 'react-native';
import colors from './colors';
const globalStyles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
paddingTop: 16,
},
header: {
fontFamily: 'sarabun-bold',
fontSize: 20,
marginVertical: 4,
lineHeight: 40,
alignSelf: 'center',
},
body: {
fontFamily: 'sarabun-regular',
fontSize: 16,
},
primary: {
color: colors.darkPurple,
},
secondary: {
color: colors.lightPurple,
},
lightGrey: {
color: colors.lightGrey,
},
darkGrey: {
color: colors.darkGrey,
},
white: {
color: colors.white,
},
black: {
color: colors.black,
},
error: {
color: colors.default.error,
},
success: {
color: colors.default.success,
},
});
export default globalStyles;
import globalStyles from './styles/global';
<View style={globalStyles.container}>
<Text>Loading</Text>
</View>;
Unfortunately, Material-UI in React is not compatible with React Native. Therefore, in order to use material design in react native, you need to use react-native-paper.
- install react-native-paper
npm install react-native-paper
- edit babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: ['react-native-paper/babel'],
},
},
};
};
You can use state like React such as useState, useEffect and much more.
Button props
- title - text in the button
- onPress - when press the button what function will be applied
- color - color code
// Button from react-native-paper
import { Button } from 'react-native-paper';
<Button
style={globalStyles.submitButton}
mode="contained"
onPress={() => closeModal()}
>
ยกเลิก
</Button>;
use <TextInput />
to create text input form
// text input from react-native-paper
import { Button, TextInput } from 'react-native-paper'
...
<TextInput
style={styles.inputField}
mode="outlined"
label="username"
value={username}
onChangeText={(username) => setUsername(username)}
/>
// text input from react-native
import { TextInput } from 'react-native';
...
<TextInput
style={globalStyles.inputField}
value={task}
onChangeText={(task) => setTask(task)}
/>
use the same logic like React
{
array.map((item) => {
return (
<View key={item.key}>
<Text>{item.name}</Text>
</View>
);
});
}
By default, react-native app cannot be scroll. Therefore, you need to import ScrollView and wrap the element that need to be scrolled.
import { TouchableWithoutFeedback } from 'react-native';
return (
...
<ScrollView>
<div></div>
</ScrollView>
);
Show alert message using Alert component from react-native
Alert.alert('ออกจากระบบ', 'ท่านต้องการออกจากระบบหรือไม่', [
{ text: 'ยกเลิก', onPress: () => null },
{
text: 'ออกจากระบบ',
onPress: () => {
deleteData('token');
navigation.navigate('Login');
},
},
]);
If you want the keyboard to be disappeared when press on the space. You need to import TouchableWithoutFeedback and Keyboard component.
import { TouchableWithoutFeedback } from 'react-native';
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss();
}}
>
...
</TouchableWithoutFeedback>;
Expo provide icons by default when you install expo init
. Look about expo icons here.
import { Ionicons } from '@expo/vector-icons';
export default function App() {
return (
<View style={styles.container}>
<Ionicons name="md-checkmark-circle" size={32} color="green" />
</View>
);
}
- Add font with .ttf format in the assets/fonts
- Use Font.loadAsync() to load fonts
- Use AppLoading component to show something while font is loading
- If you get error with expo-app-loading, you can install it by
expo install expo-app-loading
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Font from 'expo-font';
import AppLoading from 'expo-app-loading';
import Login from './screens/Login';
const getFonts = () =>
Font.loadAsync({
'sarabun-thin': require('../assets/fonts/Sarabun-Thin.ttf'),
'sarabun-light': require('../assets/fonts/Sarabun-Light.ttf'),
'sarabun-regular': require('../assets/fonts/Sarabun-Regular.ttf'),
'sarabun-bold': require('../assets/fonts/Sarabun-Bold.ttf'),
});
const App: React.FC = () => {
const [fontsLoaded, setFontsLoaded] = useState(false);
if (fontsLoaded) {
return <Login />;
} else {
return (
<AppLoading
startAsync={getFonts}
onFinish={() => setFontsLoaded(true)}
onError={() => console.log('error')}
>
<View style={styles.container}>
<Text>Hello World!</Text>
</View>
</AppLoading>
);
}
};
Add Routing and Navigation in your project
npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack
// Routes.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { MaterialIcons } from '@expo/vector-icons';
// screens
import LoginScreen from '../screens/LoginScreen';
import TodoListScreen from '../screens/TodoListScreen';
import { logout } from '../utils';
// initialize stack
const Stack = createNativeStackNavigator();
const Routes: React.FC = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Login">
<Stack.Screen
name="Login"
component={LoginScreen}
options={{
// config header
headerTitle: 'Todo List Application',
}}
/>
<Stack.Screen
name="TodoList"
component={TodoListScreen}
options={({ navigation }) => ({
headerTitle: 'Todo List Application',
headerLeft: () => <></>,
headerRight: () => (
// use icon as header
<MaterialIcons
name="logout"
size={24}
color="black"
onPress={() => logout(navigation)}
/>
),
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
Use to store data in mobile storage like cookie on web browser
npm install @react-native-async-storage/async-storage
// storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storeData = async (name: string, value: Object) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem('@' + name, jsonValue);
} catch (e) {
console.log(e);
}
};
export const getData = async (name: string) => {
try {
const jsonValue = await AsyncStorage.getItem('@' + name);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
export const deleteData = async (name: string) => {
try {
const jsonValue = await AsyncStorage.removeItem('@' + name);
} catch (e) {
console.log(e);
}
};
- use
expo build:android
for building android app or useexpo build:ios
for building ios app - Expo will ask you to login with Expo account
- Choose apk or abb (app bundle)
- Build app
- After building app is completed, you can download apk or abb file on your local pc.