React Native skeleton project.
-
Install Expo CLI
npm install -g expo-cli # in the root directory -
Install Expo client for iOS and Android
-
Clone repository
git clone https://github.com/FutureOfRussia/rn-skeleton.git
-
Install project dependencies
Navigate to the project folder in your terminal
cd rn-skeletonand runnpm install. -
Starting the development server
Type
expo startto start the local development server of Expo CLI. -
Opening the app on your phone/tablet
- 🍎 On your iPhone or iPad, open the default Apple "Camera" app and scan the QR code you see in the terminal or in Expo Dev Tools.
- 🤖 On your Android device, press "Scan QR Code" on the "Projects" tab of the Expo client app and scan the QR code you see in the terminal or in Expo Dev Tools.
Button with bouncing animation. Based on Pressable component from React Native and React Native Reanimated animation.
<BounceButton onPress={() => console.log('Hello World!')}>
<Text>Press</Text>
</BounceButton>Determines if the debounce module is enabled. To use this module, an useDebounce hook must be placed in the root component.
| Type | Required | Default |
|---|---|---|
| bool | No | false |
Specifies the disabled state of the button.
| Type | Required | Default |
|---|---|---|
| bool | No | false |
Enabled vibration when pressed and released.
| Type | Required | Default |
|---|---|---|
| bool | No | false |
Sets additional distance outside of element in which a press can be detected.
| Type | Required | Default |
|---|---|---|
| number | No | 10 px |
Press event callback.
| Type | Required |
|---|---|
| function | No |
Supports animated styles.
| Type | Required |
|---|---|
| style | No |
Button with opacity animation. Based on Pressable component from React Native.
<OpacityButton onPress={() => console.log('Hello World!')}>
<Text>Press</Text>
</OpacityButton>Determines if the debounce module is enabled. To use this module, an useDebounce hook must be placed in the root component.
| Type | Required | Default |
|---|---|---|
| bool | No | false |
Specifies the disabled state of the button.
| Type | Required | Default |
|---|---|---|
| bool | No | false |
Sets additional distance outside of element in which a press can be detected.
| Type | Required | Default |
|---|---|---|
| number | No | 10 px |
Long press event callback.
| Type | Required |
|---|---|
| function | No |
Press event callback.
| Type | Required |
|---|---|
| function | No |
| Type | Required |
|---|---|
| style | No |
Component for displaying local push notifications.
In the root component:
import { LocaleNotification } from './components/LocaleNotification'
return (
<View>
<App />
<LocaleNotification />
</View>
)Call from anywhere:
import { pushNotification } from '../LocaleNotification'
return (
<BounceButton onPress={() => pushNotification({ msg: 'Hello World!' })}>
<Text>Press</Text>
</BounceButton>
)pushNotification(notification) - Push a new Notification to the event loop.
Arguments:
- notification (Notification) -- Notification object.
- msg (string - require) - Notification text.
- action (function) - If passed, the action button will be displayed. Callback of the action button.
- actionLabel (string) - Action button label. Default value - 'Ok'.
Component for managing Expo OTA updates. If there is an available update for the application, a banner will be shown with which you can install this update and restart the application. Uses expo update module.
In the root component:
import { UpdateBanner } from './components'
return (
<View>
<App />
<UpdateBanner />
</View>
)Used to process requests to the server and its responses.
import AsyncStorage from '@react-native-async-storage/async-storage'
import axios, { AxiosInstance } from 'axios'
import { apiEndpoint } from '../constants'
export default function HttpClient(): AxiosInstance {
const config = {
baseURL: `${apiEndpoint}`, // base path to API
headers: { 'Content-Type': 'application/json' },
}
const axiosInstance = axios.create(config)
axiosInstance.interceptors.request.use(async (_conf) => {
// get authorization token from device storage
const token = JSON.parse((await AsyncStorage.getItem('token')) || '')
const conf = _conf
// checking the token and adding it to the request header
if (token && moment.unix(token.expiration_date).isAfter(moment())) {
conf.headers.Authorization = `Bearer ${token.access_token}`
}
return conf
}, (error) => Promise.reject(error))
axiosInstance.interceptors.response.use((response) => response, async (error) => {
console.log(error) // here you can process the server response
return Promise.reject(error)
})
return axiosInstance
}const getUserImages = async (id: number) => {
const { data } = await HttpClient.get('api/images', { params: { user_id: id } })
console.log(data)
}// types.ts
export type Values = {
title: string
}
export type State = {
values: Values
}
export type Dispatch = {
values: {
changeTitle: (payload: string) => Values
changeTitleAsync: (payload: string) => Promise<void>
}
}The model brings together state, reducers, async actions & action creators in one place.
// values.ts
import { Values, Dispatch, State } from './types'
const initialState: Values = {
title: "It's React Native project!", // initial state
}
export default {
state: initialState,
reducers: {
// handle state changes with pure functions
changeTitle: (state: Values, payload: string): Values => ({ ...state, title: payload }),
},
effects: (dispatch: Dispatch) => ({
// handle state changes with impure functions.
// use async/await for async actions
changeTitleAsync: async (payload: string, state: State) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
dispatch.main.changeTitle(payload)
},
}),
}Connect all models to the store.
// store.ts
import { init } from '@rematch/core'
import values from './values'
export default init({ models: { values } })Wrap the root component in a provider.
// App.tsx
import { Provider } from 'react-redux'
import store from './store'
return (
<View>
<Provider store={store}>
<App />
</Provider>
</View>
)Use hooks from react-redux to get and modify store data.
import { useDispatch,useSelector } from 'react-redux'
import { Dispatch, State, Values } from './types'
function TextInputWithRedux() {
const { state: { changeTitle } } = useDispatch<Dispatch>()
const { title } = useSelector<State, Values>((state) => state.values)
return <TextInput value={title} onChangeText={(value) => changeTitle(value)} />
}