How do you achieve loading resources while showing the splash screen when you are using functional components with hooks? What is the pattern in using apploading and/or splashscreen with hooks?
Thanks!
Bill
If you only understand Hook's useState, this is a very easy change. This is simply converted into a function, and the state value is resolved using hooks. If you change the example of AppLoading to Hook, the code below is as follows.
AppLoading use Hooks
import React, { useState } from 'react';
import { View ,Image } from "react-native";
import { Asset } from 'expo-asset';
import { AppLoading } from 'expo';
export default function App() {
const [isReady, setReady] = useState(false);
const _cacheResourcesAsync = async () => {
const images = [require('./assets/snack-icon.png')];
const cacheImages = images.map(image => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all(cacheImages);
}
return (
isReady === false ? ( <AppLoading
startAsync={_cacheResourcesAsync}
onFinish={() => setReady(true)}
onError={console.warn}
/>) : (<View style={{ flex: 1 }}>
<Image source={require('./assets/snack-icon.png')} />
</View>)
);
}
Related
I'm using an expo camera for QRcode scan with react-navigation. it works fine only at first after that use of second time the screen of the camera became black. It can not work. but with refresh the app it work fine once
can any suggest me the best solution for it?
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import { Camera } from 'expo-camera';
import {SetHeight,SetWidth}from '../utillies/Display'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import Entypo from 'react-native-vector-icons/Entypo'
import { withNavigationFocus } from 'react-navigation'
const QrcodeReader = ({navigation,isFocused}) => {
const [hasPermission, setHasPermission] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
useEffect(() => {
(async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
if (hasPermission === null) {
return <View />;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return(
<View style={styles.container}>
{isFocused&& <Camera
type={type}
onBarCodeScanned={(...args) => {
const data = args[0].data;
const result = JSON.stringify(data);
navigation.navigate('ItemCollector',{FKUserId:result});
}}
barCodeScannerSettings={{
barCodeTypes: ['qr'],
}}
style={{ flex: 1 }}
>
</Camera>}
</View>
);
}
export default withNavigationFocus(QrcodeReader);
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>
);
}
Im trying to implement a Logout button in the DrawerNavigator, which should logout via a function in my context and should then change the navigation with the useNavigation hook. However when it loads the component, I get the following error:
Here is my Code which im trying to execute:
import React, { useContext } from "react";
import { View, Text, Alert, StyleSheet, TouchableOpacity } from "react-native";
import {
DrawerContentScrollView,
DrawerItemList,
DrawerItem,
} from "#react-navigation/drawer";
import { useNavigation } from "#react-navigation/native";
import AuthContext from "../store/auth-context";
const CustomSidebarMenu = (props) => {
const navigation = useNavigation();
const authCtx = useContext(AuthContext);
return (
<View style={stylesSidebar.sideMenuContainer}>
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<TouchableOpacity
style={stylesSidebar.buttonStyle}
onPress={() => {
authCtx.logout();
navigation.replace("Login");
}}
activeOpacity={0.5}
>
<Text style={stylesSidebar.buttonTextStyle}>Logout</Text>
</TouchableOpacity>
</DrawerContentScrollView>
</View>
);
};
export default CustomSidebarMenu;
useCallback code:
const logoutHandler = useCallback(async () => {
setToken(null);
await AsyncStorage.removeItem('token');
await AsyncStorage.removeItem('expirationTime');
if (logoutTimer) {
clearTimeout(logoutTimer);
}
}, []);
From apollo docs: Note that you cannot use the useNavigation hook inside the drawerContent since useNavigation is only available inside screens. You get a navigation prop for your drawerContent which you can use instead
Try props.navigation.replace("Login"); instead
Same issue you are facing on Github here as well: https://github.com/react-navigation/react-navigation/issues/7725
I'm doing a project in react native but i have an issue with my expo google font. I'm pretty new to expo so I have basically no idea on how to fix this bug. The bug only appears on IOS (don't know about android) and works fine on web. The bug appears to be inside useFonts.js which is a file that's part of the expo-google-font.
One of my files using expo-google-font.
import React from "react";
import { Text, TouchableOpacity, Image, StyleSheet } from "react-native";
import { useFonts, Inter_300Light } from "#expo-google-fonts/inter";
import { DefaultWidth } from "./DefaultWidth";
export default function AppleButton() {
let [fontsLoaded] = useFonts({ Inter_300Light });
if (!fontsLoaded) {
return null;
}
return (
<TouchableOpacity style={styles.appleBtnWrapper}>
<Image
style={styles.appleBtnImage}
source={require("../../../assets/third-party-icon/apple.png")}
/>
<Text style={styles.appleBtnText}>Fortsæt med Apple</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
appleBtnText: {
color: "#fff",
fontWeight: "bold",
textAlign: "center",
fontFamily: "Inter_300Light",
fontSize: 16,
},
});
The useFonts.js use useEffect. These are the bugs I'm getting:
Here is the useFonts.js
import { useEffect, useState } from 'react';
import { loadAsync } from 'expo-font';
/**
* Load a map of custom fonts to use in textual elements.
* The map keys are used as font names, and can be used with `fontFamily: <name>;`.
* It returns a boolean describing if all fonts are loaded.
*
* Note, the fonts are not "reloaded" when you dynamically change the font map.
*
* #see https://docs.expo.io/versions/latest/sdk/font/
* #example const [loaded, error] = useFonts(...);
*/
export function useFonts(map) {
let [loaded, setLoaded] = useState(false);
let [error, setError] = useState(null);
useEffect(() => {
loadAsync(map)
.then(() => setLoaded(true))
.catch(setError);
}, []);
return [loaded, error];
}
Here is my App.tsx
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import {registerRootComponent} from 'expo'
import Navigation from './routes'
export default function App() {
return (
<Navigation/>
);
}
registerRootComponent(App);
Firstly install expo-app-loading from here
Then For your fonts create a folder called hooks where your App.js is located and inside that create a file useFonts.js
In useFonts.js write like this -
import * as Font from 'expo-font';
import { Inter_300Light } from '#expo-google-fonts/inter';
export default useFonts = async () => {
await Font.loadAsync({
Inter_300Light: Inter_300Light,
});
};
Your App.tsx should look like this
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { registerRootComponent } from 'expo';
import AppLoading from 'expo-app-loading';
import useFonts from './hooks/useFonts';
import Navigation from './routes';
export default function App() {
const [IsReady, SetIsReady] = useState(false);
const FontLoading = async () => {
await useFonts(); // Font is being loaded here
};
if (!IsReady) {
return (
<AppLoading
startAsync={FontLoading}
onFinish={() => SetIsReady(true)}
onError={() => {}}
/>
);
}
return <Navigation />;
}
registerRootComponent(App);
Now if you want to use these fonts in any files then just simply write the name of the font. You don't need to Import font in every page
For example, your page which uses fonts should look like this
import React from 'react';
import { Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
import { DefaultWidth } from './DefaultWidth';
export default function AppleButton() {
return (
<TouchableOpacity style={styles.appleBtnWrapper}>
<Image
style={styles.appleBtnImage}
source={require('../../../assets/third-party-icon/apple.png')}
/>
<Text style={styles.appleBtnText}>Fortsæt med Apple</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
appleBtnText: {
color: '#fff',
fontWeight: 'bold',
textAlign: 'center',
fontFamily: 'Inter_300Light', // Name of the font..Simple
fontSize: 16,
},
});
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).