React Native: Loading Assets before app is ready threw an exception - react-native

function cacheImages(images) {
return images.map(image => {
if (typeof image === 'string') {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
}
function cacheFonts(fonts) {
return fonts.map(font => Font.loadAsync(font));
}
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = { isReady : false };
}
async _loadAssetsAsync () {
const imageAssets = cacheImages(require('./assets/icon.png'));//['https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png']);
const fontAssets = cacheFonts([FontAwesome.font]);
await Promise.all([...imageAssets, ...fontAssets]);
}
render() {
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._loadAssetsAsync}
onFinish={() => this.setState({ isReady: true })}
onError={alert('Error loading assets')}/>
);
}
return (
<Provider store={Store}>
<View style={{ flex:1, width: '100%', height: '100%' }}>
<Navigator></Navigator>
<LoadingModal></LoadingModal>
</View>
</Provider>
);
I tried to use the expo provided code on their website to prefetch the assets and other related images before the application load.
i received an error after rendered the Apploading element. The exception does not show any particular error which make any sense.
AppLoading threw an unexpected error when loading:
cacheImages#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false&assetPlugin=%2FUsers%2Fsimonlam%2FDesktop%2Freact_native%2F

Remove the alert in the AppLoading onError method and use console.error instead,
There's a parsing error on that line.

You can use-
onError={(error)=> console.warn(error)}
this will make your code run

Related

React Native WebView App not exit on pressing back button

React Native WebView App not exit on pressing back button after setting Go back functionality on back button pressed. I want go back functionality on pressing back button when webview is not on home page and when webview is on home page then exit the app.
export default class WebView extends Component {
constructor (props) {
super(props);
this.WEBVIEW_REF = React.createRef();
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = ()=>{
this.WEBVIEW_REF.current.goBack();
return true;
}
onNavigationStateChange(navState) {
this.setState({
canGoBack: navState.canGoBack
});
}
render(){
return (
<WebView
source={{ uri: 'https://stackoverflow.com' }}
ref={this.WEBVIEW_REF}
onNavigationStateChange={this.onNavigationStateChange.bind(this)}
/>
);
}
}
Since you are managing the state of canGoBack inside onNavigationStateChange function, Change your handleBackButton function as below,
handleBackButton = () => {
if (this.state.canGoBack) {
this.WEBVIEW_REF.current.goBack();
return true;
}
}
Check below complete example
import React, { Component } from "react";
import { BackHandler } from "react-native";
import { WebView } from "react-native-webview";
export default class App extends Component {
WEBVIEW_REF = React.createRef();
state = {
canGoBack: false,
};
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton = () => {
if (this.state.canGoBack) {
this.WEBVIEW_REF.current.goBack();
return true;
}
};
onNavigationStateChange = (navState) => {
this.setState({
canGoBack: navState.canGoBack,
});
};
render() {
return (
<WebView
source={{ uri: "https://stackoverflow.com" }}
ref={this.WEBVIEW_REF}
onNavigationStateChange={this.onNavigationStateChange}
/>
);
}
}
Hope this helps you. Feel free for doubts.
I had this problem for quite a while but i have managed to resolve it. Problem that I experienced was that goBack (which is used as back event) function was triggered before onNavigationStateChange but somehow state was change although goBack function was called first.
const HomeScreen = () => {
const {web} = config;
const ref = useRef();
const [canGoBack, setCanGoBack] = useState(false);
const setupState = event => {
setCanGoBack(event?.canGoBack);
};
useEffect(() => {
const goBack = () => {
if (canGoBack === false) {
Alert.alert(
'Exit App',
'Do you want to exit app?',
[
{text: 'No', onPress: () => console.log('No'), style: 'cancel'},
{text: 'Yes', onPress: () => BackHandler?.exitApp()},
],
{cancelable: false},
);
}
ref?.current?.goBack();
return true;
};
BackHandler?.addEventListener('hardwareBackPress', () => goBack());
return () =>
BackHandler?.removeEventListener('hardwareBackPress', () => goBack());
}, [canGoBack]);
return (
<View style={styles.mainContainer}>
{/* last version 11.21.1 */}
<WebView
ref={ref}
source={{uri: web?.url}}
style={{flex: 1}}
cacheEnabled={web.cacheEnabled}
automaticallyAdjustContentInsets={false}
domStorageEnabled={true}
startInLoadingState={true}
allowsInlineMediaPlayback={true}
allowsBackForwardNavigationGestures
onNavigationStateChange={e => setupState(e)}
/>
</View>
);
};
export default HomeScreen;

#expo/vector-icons, how to load font using react-navigation

I am trying to use this with react-navigation and I keep getting:
fontFamily 'Feather' is not a system font and has not been loaded
through Expo.Font.loadAsync
I am using the Font.loadAsync() method in my loading component but the error keeps showing up. I actually ended up removing all vector icons from my app in order to start clean.
So my question is if I have a switch navigator which starts with a loading screen (this starts the auth check) and switches either to login screen or root tab container which contains the meat of my app. I cant seem to figure out where to call Font.loadAsync()
Here is my MainNavigator.js
export default createBottomTabNavigator(
{
Friends: FriendsList,
Profile: Profile,
Settings: Settings
},
{
defaultNavigationOptions: ({navigation}) => ({
tabBarIcon: ({tintColor}) => {
const {routeName} = navigation.state;
let iconName;
if (routeName == "Friends") {
iconName = "users"
} else if (routeName == "Profile") {
iconName = "user"
} else if (routeName == "Settings") {
iconName = "settings"
}
return <Feather name={iconName} size={20} color={tintColor} />
}
}),
tabBarOptions: {
style: {
backgroundColor: "#fff"
},
inactiveTintColor: "#999",
activeTintColor: TERTIARY_COLOR
}
}
)
App.js
// Screens for Switch Navigator
import AuthScreens from "./components/screens/AuthScreens";
import LoadingScreen from "./components/screens/Loading";
import MainNavigator from "./MainNavigator";
const AppContainer = createAppContainer(createSwitchNavigator(
{
LoadingScreen,
AuthScreens,
MainNavigator
},
{
initialRouteName: "LoadingScreen"
}
))
Loading Screen
export default class LoadingScreen extends Component {
async componentDidMount() {
try{
await Font.loadAsync({
FeatherFont
})
firebase.auth().onAuthStateChanged(user => {
this.props.navigation.navigate(user ? "MainContainer" : "LoginScreen")
})
} catch(error) {
alert(error)
}
}
render() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center"}}>
<ActivityIndicator/>
</View>
)
}
}
You need to load them in the App.js inside _loadResourcesAsync unsing the Font.loadAsync()
_loadResourcesAsync = async () => {
// blablabla
return Promise.all([
Asset.loadAsync([
require('random images'),
]),
Font.loadAsync(
// this line is where you load the font, name and path
{Feather: require('./assets/fonts/Feather-path.ttf')},
),
]);
};
And then inside the render function :
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<AppNavigator style={styles.container} />
<BarraOffline connected={this.state.isConnected}/>
</View>
);
}
}
I don't know your definition of FeatherFont,
but I don't think Font recognize it as a font.
Font is the module that deals with font-related tasks.
First, we must load the font from our assets directory using
Expo.Font.loadAsync().
We can do this in the componentDidMount() lifecycle method of the
App component.
Add the following method in App:
Now that we have the font files saved to disk and the Font SDK
imported, let's add this code:
We need a way to re-render the Text component when the font has finished loading.
First we initialize fontLoaded to false in the App class
constructor:
class App extends React.Component {
state = {
fontLoaded: false,
};
async componentDidMount() {
await Font.loadAsync({
'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
});
this.setState({ fontLoaded: true });
}
}
With React Native you specify fonts in Text components using the fontFamily style property. The fontFamily is the key that we used with Font.loadAsync.
Usage
<Text style={{ fontFamily: 'open-sans-bold', fontSize: 56 }}>
Hello, world!
</Text>

Update props from other component in react native

I have a Main class which I show an array to user, then in detail page user can edit each element which I'm passing using react navigation parameter. I want to edit my array in the detail class and save it using async storage.
//Main.jsimport React from 'react';
import {
StyleSheet ,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity,
KeyboardAvoidingView,
AsyncStorage
} from 'react-native'
import Note from './Note'
import detail from './Details'
import { createStackNavigator, createAppContainer } from "react-navigation";
export default class Main extends React.Component {
static navigationOptions = {
title: 'To do list',
headerStyle: {
backgroundColor: '#f4511e',
},
};
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
}
async saveUserTasks(value) {
try {
await AsyncStorage.setItem('#MySuperStore:userTask',JSON.stringify(value));
} catch (error) {
console.log("Error saving data" + error);
}
}
getUserTasks = async() =>{
try {
const value = await AsyncStorage.getItem('#MySuperStore:userTask');
if (value !== null){
this.setState({ noteArray: JSON.parse(value)});
}
} catch (error) {
console.log("Error retrieving data" + error);
}
}
render() {
this.getUserTasks()
let notes = this.state.noteArray.map((val,key) => {
return <Note key={key} keyval={key} val={val}
deleteMethod={ () => this.deleteNote(key)}
goToDetailPage= {() => this.goToNoteDetail(key)}
/>
});
const { navigation } = this.props;
return(
<KeyboardAvoidingView behavior='padding' style={styles.keyboard}>
<View style={styles.container}>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<View style={styles.footer}>
<TextInput
onChangeText={(noteText) => this.setState({noteText})}
style={styles.textInput}
placeholder='What is your next Task?'
placeholderTextColor='white'
underlineColorAndroid = 'transparent'
>
</TextInput>
</View>
<TouchableOpacity onPress={this.addNote.bind(this)} style={styles.addButton}>
<Text style={styles.addButtonText}> + </Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
addNote(){
if (this.state.noteText){
var d = new Date();
this.state.noteArray.push({
'creationDate': d.getFullYear() + "/" + (d.getMonth()+1) + "/" + d.getDay(), 'taskName': this.state.noteText,'dueDate':'YYYY/MM/DD'
});
this.setState({noteArray:this.state.noteArray})
this.setState({noteText: ''});
this.saveUserTasks(this.state.noteArray)
}
}
deleteNote(key){
this.state.noteArray.splice(key,1);
this.setState({noteArray: this.state.noteArray})
this.saveUserTasks(this.state.noteArray)
}
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
selectedTask: this.state.noteArray[key],
});
}
}
in detail view I have this method which is similar to add note in main class:
export default class Details extends React.Component {
render() {
const { navigation } = this.props;
const selectedTask = navigation.getParam('selectedTask', 'task');
return(
<View key={this.props.keyval} style={styles.container}>
<TouchableOpacity onPress={this.saveEdit.bind(this)} style={styles.saveButton}>
<Text style={styles.saveButtonText}> save </Text>
</TouchableOpacity>
</View>
);
}
saveEdit(){
let selectedItem = { 'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData}
this.props.navigation.state.params.saveEdit(selectedItem)
}
}
How can I change my props in any component?
First of all you shouldn't call this.getUserTasks() in the render method because the function has this.setState which is bad and could end in a endless loop I guess or at least effect in worse performance. You could instead call it in componentDidMount:
componentDidMount = () => {
this.getUserTasks();
}
Or alternatively call already in constructor but I prefer the first option:
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
this.getUserTasks()
}
this.props.noteArray.push({.. is probably undefined because you aren't passing it down any where. (Didn't see any reference in your snippet). I guess I would implement the saveEdit function in the Main.js component and simply pass it down to the navigation route and call the function in Details component by accessing the navigation state props:
Update
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
// selectedTask: this.state.noteArray[key],
selectedItem: key,
saveEdit: this.saveEdit
});
}
saveEdit(selectedItem){
const selectedTask = this.state.noteArray[selectedItem]
this.state.noteArray.push({
'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData
});
this.setState({noteArray:this.state.noteArray})
this.setState({dueData: 'YYYY/MM/DD'});
this.saveUserTasks(this.state.noteArray)
}
And then call saveEdit in Details Component:
saveSelectedItem = () => {
const { navigation } = this.props.navigation;
const {selectedItem, saveEdit} = navigation.state && navigation.state.params;
saveEdit(selectedItem)
}

React Native how replace image url on error?

I have an app which displays multiple images which I load from the API. Now the problem is some of the images are expired which is causing a problem on Android, in Android the screen starts lagging as soon as the expired image loads on the screen.
I have tried replacing the image source with onError={() => this.imgRefs[img_unique_id].setNativeProps({src: [{uri: this.state.workingUri}]})} this method but its not working.
I cannot use the local state as it is not saving the output in the local state.
I have tried the following code
<Image
source={image.url}
progressiveRenderingEnabled={true}
ref={item.id}
onError={(e) => this.refs[item.id].setNativeProps({source: [{uri: "working image URL"}]})}
resizeMethod={"scale"}>
</Image>
The above code gives me an undefined setNativeProps error, and if I do not use the onError on android it shows me memory leak error.
Here is a complete example of that. To have own state for every FlatList item, I created a class.
import React, { Component, PureComponent } from 'react';
import { FlatList, Text, View, Image } from 'react-native';
class ItemClass extends PureComponent {
state = {
isImageExit: null
}
componentWillMount = () => {
const { task } = this.props;
Image.getSize(task.url, (width, height) => {
if(width>0){
this.setState({ isImageExit: true });
}
else{
this.setState({ isImageExit: false });
}
}, () => {
this.setState({ isImageExit: false });
});
}
render() {
const { isImageExit } = this.state;
const { task } = this.props;
const url = isImageExit ? task.url : 'https://dummyimage.com/600x400/000/fff';
if (isImageExit === null) {
return null;
}
return (
<Image
style={{ height: 200, width: 200 }}
source={{ uri: url }}
/>
);
}
}
export default class App extends Component {
render() {
const data = [
{ url: 'url' },
{ url:'https://cdn.pixabay.com/photo/2017/08/05/18/53/mountain-2585069_1280.jpg' },
];
return (
<View style={{alignItems: 'center', top: 50}}>
<FlatList
data={data}
renderItem={({ item }) => <ItemClass task={item} />}
/>
</View>
);
}
}
I think you should use data received from Api and set state accordingly inside componentWillReceiveProps because I think setting state is the best way to achieve this result.
.
this.setState = { image: { uri: 'image_uri_from_api' } }
Inside <Image> add -
<Image style={ your_custom_style }
source={ this.state.image }
onError={ this.onError.bind(this) }
/>
Inside onError add this -
onError(error){
this.setState({ image: require('your_local_image.path')})
}
Hope it works now !

How to Control async method react-native

I want to send data to another component. I get datas from
AsyncStorage.getItem('myKey');
But when start async, component start to render so it sends null data to another component.
here is my methods ;
componentWillMount(){
this.getdata();
}
getdata = async () => {
console.log("console1");
const value = await AsyncStorage.getItem('myKey');
console.log("console2");
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
and this is my render method ;
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
);
}
I send datas to LoginForm. I want to ask one more question. If I use <LoginForm /> like this, it ruins my component. How can I send with different way ?
Only render if it's ready to render. the way I do it is initialize a state lets say isReady and set to false then set it to true when you have the value.
Would look like this:
export default class test extends Component {
constructor(props) {
super(props)
this.state = {
isReady:false
}
}
componentWillMount(){
this.getdata();
}
getdata = async () => {
const value = await AsyncStorage.getItem('myKey');
this.setState({isReady:true})
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
render() {
if(this.state.isReady){
return (
<View ref={ref => this.view = ref} onLayout={() => this.saveLayout()}>
</View>
)}else{
<View></View>
}
}
}
To your second question:
If you pass through LoginForm you can create a function there that gets the parameters and updates state, then pass that function to your other component and call the function with the values in paremeter. if you are using react navigation you can do it like so:
loginForm
updateValues(values){
this.setState({value:values})
}
To pass the function with react-navigation:
this.props.navigation.navigate('otherComponent',{updateValues:this.updateValues})
In your otherComponent you call the function like so:
otherComponent
this.props.navigation.state.params.updateValues(newValues);
How about checking for the values variable in the render method?
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
this.state.values ?
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
: <></>
);
}
You can keep a default/initial values in state variable at first like this:
constructor(props){
this.state = {
values:{userName: '', password: ''}
}
}
And when the actual values are available you can set them in state and automatically re-rendering will occur.
Since AsyncStorage returns a promise you can use .then() syntax
componentDidMount(){
console.log("console1");
AsyncStorage.getItem('myKey').then(value=>{
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}).catch(err=>{
console.log('err', err);
})
}