How to detect first launch in react-native - react-native

What is a good way to detect the first and initial launch of an react-native app, in order to show an on-boarding/introductory screen ?

Your logic should follow this:
class MyStartingComponent extends React.Component {
constructor(){
super();
this.state = {firstLaunch: null};
}
componentDidMount(){
AsyncStorage.getItem("alreadyLaunched").then(value => {
if(value === null){
AsyncStorage.setItem('alreadyLaunched', 'true'); // No need to wait for `setItem` to finish, although you might want to handle errors
this.setState({firstLaunch: true});
}
else{
this.setState({firstLaunch: false});
}}) // Add some error handling, also you can simply do this.setState({fistLaunch: value === null})
}
render(){
if(this.state.firstLaunch === null){
return null; // This is the 'tricky' part: The query to AsyncStorage is not finished, but we have to present something to the user. Null will just render nothing, so you can also put a placeholder of some sort, but effectively the interval between the first mount and AsyncStorage retrieving your data won't be noticeable to the user.
}else if(this.state.firstLaunch === 'true'){
return <FirstLaunchComponent/>
}else{
return <NotFirstLaunchComponent/>
}
}
Hope it helps.

I made some adjustments to martinarroyo's suggestion. AsyncStorage.setItem should set a string value and not a bool.
import { AsyncStorage } from 'react-native';
const HAS_LAUNCHED = 'hasLaunched';
function setAppLaunched() {
AsyncStorage.setItem(HAS_LAUNCHED, 'true');
}
export default async function checkIfFirstLaunch() {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
if (hasLaunched === null) {
setAppLaunched();
return true;
}
return false;
} catch (error) {
return false;
}
}
This function can then be imported wherever you need it. Note that you should render null (or something else clever) while waiting for the async function to check AsyncStorage.
import React from 'react';
import { Text } from 'react-native';
import checkIfFirstLaunch from './utils/checkIfFirstLaunch';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isFirstLaunch: false,
hasCheckedAsyncStorage: false,
};
}
async componentWillMount() {
const isFirstLaunch = await checkIfFirstLaunch();
this.setState({ isFirstLaunch, hasCheckedAsyncStorage: true });
}
render() {
const { hasCheckedAsyncStorage, isFirstLaunch } = this.state;
if (!hasCheckedAsyncStorage) {
return null;
}
return isFirstLaunch ?
<Text>This is the first launch</Text> :
<Text>Has launched before</Text>
;
}
}

In 2022: Note that AsyncStorage from react-native is already deprecated.
Use react-native-async-storage/async-storage instead.
Custom hook:
import React, { useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
async function checkIfFirstLaunch() {
try {
const hasFirstLaunched = await AsyncStorage.getItem("#usesr_onboarded");
if (hasFirstLaunched === null) {
return true;
}
return false;
} catch (error) {
return false;
}
}
const useGetOnboardingStatus = () => {
const [isFirstLaunch, setIsFirstLaunch] = useState(false);
const [isFirstLaunchIsLoading, setIsFirstLaunchIsLoading] = useState(true);
React.useEffect(async () => {
const firstLaunch = await checkIfFirstLaunch();
setIsFirstLaunch(firstLaunch);
setIsFirstLaunchIsLoading(false);
}, []);
return {
isFirstLaunch: isFirstLaunch,
isLoading: isFirstLaunchIsLoading,
};
};
export default useGetOnboardingStatus;
Usage:
import useGetOnboardingStatus from "../../utils/useGetOnboardingStatus";
const { isFirstLaunch, isLoading: onboardingIsLoading } =
useGetOnboardingStatus();
As Eirik Fosse mentioned: You can use onboardingIsLoading to return null while waiting for the response.

Related

Problem while using Expo-av Library, App crashes while changing the songs

I am running my application on an android device. So I am using Context API, the song is in context, whenever a user clicks on a song it is updated in context and every component gets it and then useEffect of PlayerWidget gets triggered and that time my app is crashing. At the Start of the app, the song is null.
import React, { useEffect, useState, useContext } from "react";
import { View, Text, Image, TouchableOpacity } from "react-native";
import tailwind from "tailwind-rn";
import { AntDesign, FontAwesome, Foundation } from "#expo/vector-icons";
import { Audio } from "expo-av";
import { Sound } from "expo-av/build/Audio/Sound";
import { AppContext } from "../AppContext";
function PlayerWidget() {
const { song } = useContext(AppContext);
useEffect(() => {
if (song) {
console.log("player", song);
console.log("a");
playCurrentSong();
}
}, [song]);
//const navigation = useNavigation();
const [sound, setSound] = (useState < Sound) | (null > null);
const [isPlaying, setIsPlaying] = useState < boolean > false;
const [duration, setDuration] = (useState < number) | (null > null);
const [position, setPosition] = (useState < number) | (null > null);
/* useEffect(() => {
playCurrentSong();
}, []);
*/
const playBackStatusUpdate = (status: any) => {
setPosition(status.positionMillis);
setDuration(status.durationMillis);
};
const playCurrentSong = async () => {
if (!song) return;
if (sound) {
console.log(sound);
await sound.unloadAsync();
}
const { sound: newSound } = await Audio.Sound.createAsync(
{ uri: song.songUri },
{ shouldPlay: isPlaying },
playBackStatusUpdate
);
setSound(newSound);
setIsPlaying(true);
};
const onPlayPausePress = async () => {
if (!sound) {
return;
}
console.log(sound);
if (isPlaying) {
await sound.pauseAsync();
setIsPlaying(false);
} else {
await sound.playAsync();
setIsPlaying(true);
}
};
if (song === null) {
return null;
}
}
export default PlayerWidget;
Can you tell me what I am doing wrong?

React Native AsyncStorage not working - now what am I doing wrong?

What am I leaving out here?
At this point I just want to see it write a value and retrieve a value. By setting the variable "appType" I can see if it's working. It is not because the initial value of "appType" is not changing. I can tell this by which page is loading. I need this information upfront to determine which page is to load first.
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import AsyncStorage from '#react-native-community/async-storage';
import SpMyPage from './screens/s_mypg';
import UsMyPage from './screens/u_mypg';
import UsRegPage from './screens/u_regs';
function checkAsyncStorage(){
var appType = '';
const storeData = async () => {
try {
await AsyncStorage.setItem('random_time', '50000')
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('random_time')
if(value !== null) {
// value previously stored
appType = 'S';
}
else {
appType = 'U';
}
} catch(e) {
// error reading value
appType = 'U';
}
}
return appType;
}
function PreHomeScreen() {
var appType = checkAsyncStorage();
if (appType == 'S') {
return <SpMyPage />;
}
else {
if (appType == 'U') {
return <UsMyPage />;
}
else {
return <UsRegPage />;
}
}
}
/* ************************************************************************** */
const Stack = createStackNavigator();
function App() {
return (
<>
<NavigationContainer>
<Stack.Navigator
initialRouteName = "PreHomeScreen"
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="PreHomeScreen" component={PreHomeScreen} />
<Stack.Screen name="SpMyPage" component={SpMyPage} />
<Stack.Screen name="UsMyPage" component={UsMyPage} />
<Stack.Screen name="UsRegPage" component={UsRegPage} />
</Stack.Navigator>
</NavigationContainer>
</>
);
};
export default App;
I think the right way to import AsynStorage is
import AsyncStorage from '#react-native-community/async-storage';
Here's an example from the repo.
https://github.com/react-native-community/async-storage/blob/master/example/examples/GetSetClear.js
My original goal was to use AsyncStorage.
I coded it the way the examples showed.
But, I found that apparently I don't need to have an "async function" wrapped around the AsyncStorage methods. Hence no "await" needed either. Not sure if this is going to cause a problem but for now it seems to work.
function checkAsyncStorage(){
var appType = '';
try {
AsyncStorage.setItem('random_time', '50000')
} catch (e) {
// saving error
}
try {
const value = AsyncStorage.getItem('random_time')
if(value !== null) {
// value previously stored
appType = 'S';
}
else {
appType = '';
}
} catch(e) {
// error reading value
appType = 'U';
}
return appType;
}
// The import statement should be like:
import AsyncStorage from '#react-native-community/async-storage';
// The function to retrieve previously stored data (In this example it's "userId")
retrieveData = async () => {
try {
const userId = await AsyncStorage.getItem('userId');
if (userId != null) {
console.log(userId);
}
} catch (e) {
console.log(e);
}
};
// The function to store data (In this example it's "userId")
storeData = async () => {
try {
await AsyncStorage.setItem('userId', value);
} catch (e) {
console.log(e);
}
};

Handling Errors from Redux API Call as a Toast

So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?

react native setInterval cannot read property apply

I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.

React Native AsyncStorage fetches data after rendering

I am using AsyncStorage in ComponentWillMount to get locally stored accessToken, but it is returning the promise after render() function has run. How can I make render() wait until promise is completed? Thank you.
You can't make a component wait to render, as far as I know. What I've done in the app I'm working on is to add a loading screen until that promise from AsyncStorage resolves. See the examples below:
//
// With class component syntax
//
import React from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
class Screen extends React.Component {
state = {
isLoading: true
};
componentDidMount() {
AsyncStorage.getItem('accessToken').then((token) => {
this.setState({
isLoading: false
});
});
},
render() {
if (this.state.isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
}
//
// With function component syntax and hooks (preferred)
//
import React, { useEffect } from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
const Screen () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
AsyncStorage.getItem('accessToken').then((token) => {
setIsLoading(false);
});
}, [])
if (isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
Setting the isLoading property in state will cause a re-render and then you can show the content that relies on the accessToken.
On a side note, I've written a little library called react-native-simple-store that simplifies managing data in AsyncStorage. Hope you find it useful.
Based on react-native doc, you can do something like this:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
let STORAGE_KEY = '#AsyncStorageExample:key';
export default class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
loaded: 'false',
};
}
_setValue = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEY, 'true');
} catch (error) { // log the error
}
};
_loadInitialState = async () => {
try {
let value = await AsyncStorage.getItem(STORAGE_KEY);
if (value === 'true'){
this.setState({loaded: 'true'});
} else {
this.setState({loaded: 'false'});
this._setValue();
}
} catch (error) {
this.setState({loaded: 'false'});
this._setValue();
}
};
componentWillMount() {
this._loadInitialState().done();
}
render() {
if (this.state.loaded === 'false') {
return (
<View><Text>Loading...</Text></View>
);
}
return (
<View><Text>Main Page</Text></View>
);
}
}
you can use react-native-easy-app that is easier to use than async storage.
this library is great that uses async storage to save data asynchronously and uses memory to load and save data instantly synchronously, so we save data async to memory and use in app sync, so this is great.
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
const initCallback = () => {
// From now on, you can write or read the variables in RNStorage synchronously
// equal to [console.log(await AsyncStorage.getItem('isShow'))]
console.log(RNStorage.isShow);
// equal to [ await AsyncStorage.setItem('token',TOKEN1343DN23IDD3PJ2DBF3==') ]
RNStorage.token = 'TOKEN1343DN23IDD3PJ2DBF3==';
// equal to [ await AsyncStorage.setItem('userInfo',JSON.stringify({ name:'rufeng', age:30})) ]
RNStorage.userInfo = {name: 'rufeng', age: 30};
};
XStorage.initStorage(RNStorage, AsyncStorage, initCallback);
React-native is based on Javascript which does not support blocking functions.Also this makes sense as we don't want the UI to get stuck or seem unresponsive.
What you can do is handles this in the render function. i.e Have a loading screen re-render it as you as you get the info from the AsyncStorage