Expo AppLoading and app bootstrap data? - react-native

There is a new component <AppLoading/> that is supposed to let the splash screen remain visible while loading app resources.
The example in the doc is pretty straightforward
https://docs.expo.io/versions/latest/sdk/app-loading.html
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Asset, AppLoading } from 'expo';
export default class App extends React.Component {
state = {
isReady: false,
};
render() {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._cacheResourcesAsync}
onFinish={() => this.setState({ isReady: true })}
onError={console.warn}
/>
);
}
return (
<View style={{ flex: 1 }}>
<Image source={require('./assets/images/expo-icon.png')} />
<Image source={require('./assets/images/slack-icon.png')} />
</View>
);
}
async _cacheResourcesAsync() {
const images = [
require('./assets/images/expo-icon.png'),
require('./assets/images/slack-icon.png'),
];
const cacheImages = images.map((image) => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all(cacheImages)
}
}
However is this kind of component intended to handle loading resources that can fail?
Like for example if my app needs bootstrap datas regarding the authenticated users, provided by a backend, should I use this component?
If AppLoading is suited for this need, how would you handle the case where the user starts the app with no connexion, and the bootstrap data promise rejects? How do you handle retry attempts?

Related

expo AppLoading startAsync Deprecated. what is the alternative?

I am learning react native from Udemy. In one of the lessons I saw AppLoading has been used for loading fonts.
So i want to learn about it in documentation from here. I am able to use that without any issues even though, I saw here that startAsync has been deprecated.
What is the alternative to this startAsync if it stopped working?
below is the code from documentation,
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Asset } from 'expo-asset';
import AppLoading from 'expo-app-loading';
export default class App extends React.Component {
state = {
isReady: false,
};
render() {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._cacheResourcesAsync}
onFinish={() => this.setState({ isReady: true })}
onError={console.warn}
/>
); }
return (
<View style={{ flex: 1 }}>
<Image source={require('./assets/snack-icon.png')} />
</View>
);
}
async _cacheResourcesAsync() {
const images = [require('./assets/snack-icon.png')];
const cacheImages = images.map(image => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all(cacheImages);
}
}
Call _cacheResourcesAsync function in componentDidMount and when all promised are resolved set state isReady to true like:
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Asset } from 'expo-asset';
import AppLoading from 'expo-app-loading';
export default class App extends React.Component {
state = {
isReady: false,
};
componentDidMount(){
_cacheResourcesAsync();
}
render() {
if (!this.state.isReady) {
return (
<AppLoading />
); }
return (
<View style={{ flex: 1 }}>
<Image source={require('./assets/snack-icon.png')} />
</View>
);
}
_cacheResourcesAsync() {
const images = [require('./assets/snack-icon.png')];
const cacheImages = images.map(image => {
return Asset.fromModule(image).downloadAsync();
});
Promise.all(cacheImages).then(()=>{
this.setState({ isReady : true });
});
}
}
You should use a hook to load your images.
First, create a hook to load resources in a separate file:
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useState } from 'react';
export default function useCachedResources() {
const [isLoadingComplete, setLoadingComplete] = useState(false);
// Load any resources or data that we need prior to rendering the app
useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
SplashScreen.preventAutoHideAsync();
// Load images
const images = [require('./assets/snack-icon.png')];
await images.map(async image =>
await Asset.fromModule(image).downloadAsync());
} catch (e) {
// We might want to provide this error information to an error reporting service
console.warn(e);
} finally {
setLoadingComplete(true);
SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);
return isLoadingComplete;
}
Then call it in your App component - I changed it to a function component, because hooks don't work in classes and it is now the recommended way of coding react:
import { Image, View } from 'react-native';
import useCachedResources from "./hooks/useCachedResources";
export default function App() {
const isLoadingComplete = useCachedResources();
if (!isLoadingComplete) {
return null;
}
return (
<View style={{ flex: 1 }}>
<Image source={require('./assets/snack-icon.png')} />
</View>
);
}

How to emit event to app root in react native

I'm trying to implement toast message (notification) on my React Native app.
I'm Thinking about implement my Toast component inside app root, and when a button is clicked (somewhere in the app), the app root will know about it and make the toast visible.
I don't want to use a library because I have complicated UI for this and I want to include buttons inside the toast.
This is the root component - App.js:
import { Provider } from 'react-redux';
import {Toast} from './src/components/Toast';
import store from './src/store/Store.js';
import AppNavigator from './src/navigation/AppNavigator';
import StatusBar from './src/components/StatusBar';
export default function App(props) {
return (
<Provider store = { store }>
<View style={styles.container}>
<StatusBar barStyle="default"/>
<AppNavigator />
<Toast></Toast>
</View>
</Provider>
);
}
EDIT:
AppNavigator.js:
// this is how I connect each page:
let HomePage = connect(state => mapStateToProps, dispatch => mapDispatchToProps(dispatch))(HomeScreen);
let SearchPage = connect(state => mapStateToProps, dispatch => mapDispatchToProps(dispatch))(SearchScreen);
const HomeStack = createStackNavigator(
{
Home: HomePage,
Search: SearchPage,
},
config
);
const mapStateToProps = (state) => {
return {
// State
}
};
const mapDispatchToProps = (dispatch) => {
return {
// Actions
}
};
export default tabNavigator;
Any ideas how can I do it? Thanks.
For this, i would suggest to use a component to wrap your application where you have your toast. For example:
App.js
render(){
return (
<Provider store = { store }>
<View style={styles.container}>
<AppContainer/>
</View>
</Provider>
)
}
Where your AppContainer would have a render method similar to this:
render(){
return (
<Frament>
<StatusBar barStyle="default"/>
<AppNavigator />
<Toast></Toast>
</Fragment>
)
}
Then (as you are using redux) you can connect your AppContainer. After that, just make this component aware of changes on redux using componentDidUpdate
componentDidUpdate = (prevProps) => {
if(this.props.redux_toast.visible !== prevProps.redux_toast.visible){
this.setState({
toastVisible : this.props.redux_toast.visible,
toastMessage: this.props.redux_toast.message
})
}
}
This is just an example on how it could be done by using redux, I don't know how your toast or redux structure is, but it should be an available solution for your use case.
EDIT.
This is how it should look like:
//CORE
import React from 'react';
//REDUX
import { Provider } from 'react-redux';
import store from './redux/store/store';
import AppContainer from './AppContainer';
export default () => {
return (
<Provider store={store}>
<AppContainer />
</Provider>
)
}
AppContainer.js:
import React, { Component } from "react";
import { View, Stylesheet } from "react-native";
import StatusBar from "path/to/StatusBar";
import AppNavigator from "path/to/AppNavigator";
import Toast from "path/to/Toast";
import { connect } from "react-redux";
class AppContainer extends Component {
constructor(props){
super(props);
this.state={
toastVisible:false,
toastMessage:""
}
}
componentDidUpdate = (prevProps) => {
if(this.props.redux_toast.visible !== prevProps.redux_toast.visible){
this.setState({
toastVisible : this.props.redux_toast.visible,
toastMessage: this.props.redux_toast.message
})
}
}
render(){
return (
<View style={styles.container}>
<StatusBar barStyle="default"/>
<AppNavigator />
<Toast visible={this.state.toastVisible}
message={this.state.toastMessage}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container:{
flex:1
}
})
const mapStateToProps = state => ({ ...yourMapStateToProp })
const mapDispatchToProps = state => ({ ...mapDispatchToProps })
export default connect(mapStateToProps, mapDispatchToProps)(AppContainer)
Rest of the code remains untouched, you need to dispatch an action that changes a props that your appContainer's componentDidUpdate is listening to (in the example i called it redux_toast.visible).

Expo splash screen or white screen after standalone apk is installed

I know this question has been asked several times but nowhere i found the answer. So I thought I will elaborate my question. Everything works fine in development. But when I make the standalone APK the splash screen doesn't goes away. I even tried to hide it after the component is loaded. That also doesn't work. What could be the issue ?
import { SplashScreen } from 'expo';
componentDidMount() {
setTimeout(function(){
SplashScreen.hide();
},2000);
}
try it:
create a screen: SplashScreen.js
and write the code bellow:
import React, { Component } from 'react';
import { View, StatusBar, Image } from 'react-native';
import introImage from '../assets/IntroPin3.gif';
export default class Loading extends Component {
render() {
return (
<View style={{ flex: 1, justifyContent:'center', alignItems:'center', backgroundColor: '#FDFDFF' }}>
<StatusBar hidden />
<Image
style={{height:350, width: 350 }}
source={ introImage}
/>
</View>
);
}
}
and in your app.js
write it:
import React, { Component } from 'react';
import SplashScreen from './Components/SplashScreen.js';
import Intro from './Components/Menu.js';
export default class Index extends Component {
state = {
ready: false,
};
componentDidMount() {
setTimeout(() => {
this.setState({ ready: true });
}, 5530);
}
render() {
if (this.state.ready === false) {
return <SplashScreen />;
}
return <Intro />;
}
}
try it, if this help you let me know.
import SplashScreen from 'react-native-splash-screen'
export default class WelcomePage extends Component {
componentDidMount() {
// do stuff while splash screen is shown
// After having done stuff (such as async tasks) hide the splash screen
SplashScreen.hide();
}
}
try this one... also you can read the documentation:
https://github.com/crazycodeboy/react-native-splash-screen

React Native Expo Splash screen, async render

How to render react native component in the background? I want a splash screen, but the current example only allows async downloading data.
I have tried SplashScreen of AppLoading in Expo, but it only allows to download data first, it doesn't allow for async render for components.
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Asset, SplashScreen } from 'expo';
export default class App extends React.Component {
state = {
isReady: false,
};
componentDidMount() {
SplashScreen.preventAutoHide();
}
render() {
if (!this.state.isReady) {
return (
<View style={{ flex: 1 }}>
<Image
source={require('./assets/images/splash.gif')}
onLoad={this._cacheResourcesAsync}
/>
</View>
);
}
return (
// ...
);
}
_cacheResourcesAsync = async () => {
SplashScreen.hide();
// ....
await Promise.all(cacheImages);
this.setState({ isReady: true });
}
}
This only download data asynchronously, but I want to render asynchronously.

Send data from between components in react native

I'm writting a simple app have 2 screen:
Login and Main.
In file Login.js:
import React, {Component} from 'react';
import {Text, View, Button, StyleSheet, TextInput} from 'react-native';
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
user_name: '',
password: ''
};
}
render() {
return (
<View>
<Text>Username:</Text>
<TextInput
onChangeText={(text) => {
this.setState({user_name: text});
}}/>
<Text>Password:</Text>
<TextInput
secureTextEntry={true}
onChangeText={(text) => {
this.setState({password: text});
}}/>
<View style={styles.wrapper}>
<Button
onPress={this.props.click}
title="Login"
accessibilityLabel="Login to system"/>
</View>
</View>
);
}
}
Login.propType = {
user: React.PropTypes.string.isRequired,
pass: React.PropTypes.string.isRequired
}
const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around'
}
});
I have Main.js for renderScene when login successfull.
In file index.android.js, I declare class RouteTest for AppRegistry:
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Navigator,
Alert,
Button
} from 'react-native';
import Main from './Screen/Main.js';
import Login from './Screen/Login.js';
class RouteTest extends Component {
renderScene(route, navigator) {
switch (route.name) {
case 'main':
return (<Main />);
case 'login':
return (<Login
click={()=>{
if(this.state.user_name == 'abc' && this.state.password == '123') {
navigator.push({name: 'main'})
} else {
Alert.alert("Login failed");
}
}}/>);
}
}
render() {
return (<Navigator
initialRoute={{
name: 'login'
}}
renderScene={this.renderScene}/>);
}
}
AppRegistry.registerComponent('RouteTest', () => RouteTest);
When I get user_name and password from TextInput of class Login, I using:
this.state.user_name but isn't working.
And my question is: How to get a value of user's input in class Login for class RouteTest.
Thank.
you might want to use react redux. Using this u can acess anything from anywhere fast and simple.
Its a little hard to setup but once u have setup its easy to manage and u get info anywhere in the app refer this link: Getting started with Redux.
It covers different aspects of using redux. Starting off with a simple example of counter. It also deals with how to manage an api call where the result can be accessed anywhere from the app.
UPDATE: A much quicker solution will be to use React Context API. Here is a practical example . If you want a full fledged state management go with redux instead
Good luck!
Try look at https://facebook.github.io/react/docs/refs-and-the-dom.html
Basically you can access the props of Login from your RouteTest using refs set, e.g.
<Login
ref={ref => this._login = ref}
/>
After that you can access the props like this: this._login.xxx