How to restart app (react native and expo) - react-native

I use expo so I've no access to android folder.
I want to restart my app for first time. How can I do that?
I use react-native-restart, but not wroking and I have an error now:
null is not an object (evaluating 'x.default.restart;)
Codes:
componentDidMount() {
if (I18nManager.isRTL) {
I18nManager.forceRTL(false);
RNRestart.Restart();
}
}
How Can I restart my app?

I've had the same problem for over a month, nothing helped me, so I developed a library to accomplish this, simple install it using:
npm i fiction-expo-restart
and import it like:
import {Restart} from 'fiction-expo-restart';
and then when you want to perform a restart, use:
Restart();
Note in case this answer gets old, you can check the library here: https://www.npmjs.com/package/fiction-expo-restart

I have faced the same issue and found this solution somewhere.
You can try to use Updates from expo like this:
import { Updates } from 'expo';
Updates.reload();

import { StatusBar } from "expo-status-bar";
import React from "react";
import { Button, I18nManager, StyleSheet, Text, View } from "react-native";
import * as Updates from "expo-updates";
async function toggleRTL() {
await I18nManager.forceRTL(I18nManager.isRTL ? false : true);
await Updates.reloadAsync();
}
export default function App() {
return (
<View style={styles.container}>
<Text>{new Date().toString()}</Text>
<Text>{I18nManager.isRTL ? "RTL" : "LTR"}</Text>
<View style={{ marginVertical: 5 }} />
<Button title="Reload app" onPress={() => Updates.reloadAsync()} />
<View style={{ marginVertical: 5 }} />
<Button title="Toggle RTL" onPress={() => toggleRTL()} />
<StatusBar style="auto" />
</View>
);
}
https://github.com/brentvatne/updates-reload/blob/master/App.js
It's the only working way for me. When i try automatically reload app in useEffect - it crashes, so i make a separate screen where i ask user to press button to reload app

For Expo SDK 45+ please use
import * as Updates from "expo-updates"
Updates.reloadAsync()
The module fiction-expo-restart is not maintained anymore.

If you are using react-native-code-push library, you can restart with this;
import CodePush from 'react-native-code-push';
CodePush.restartApp();

What I did was to build a Restart component that is not a const but a var. And an applyReload() function that sets that var to an empty component <></> if the reload bool state is true, triggering the re-render.
The re-render will reinstate the Restart var back to its original structure, but a new instance is then created, effectively reloading everything that is inside the <Restart> tag:
My App.tsx:
export default function App() {
const [reload, setReload] = useState(false);
type Props = { children: ReactNode };
var Restart = ({ children }: Props) => {
return <>{children}</>;
};
const applyReload = () => {
if (reload) {
Restart = ({ children }: Props) => {
return <></>;
};
setReload(false);
}
};
useEffect(applyReload);
useEffect(() => {
// put some code here to modify your app..
// test reload after 6 seconds
setTimeout(() => {
setReload(true);
}, 6000);
}, []);
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<PaperProvider theme={appTheme}>
<NavigationContainer theme={appTheme} documentTitle={{ enabled: false }}>
<AppContext.Provider value={appContext}>
<Restart>
<MyMainAppComponent />
</Restart>
</AppContext.Provider>
</NavigationContainer>
</PaperProvider>
</SafeAreaView>
</SafeAreaProvider>
);
I also added the 'setReload' state function to my '<AppContext.Provider>' so anywhere down my App it is possible to trigger the App reload.

Related

React Native: Variable state not updated on first click

I am new in react-native and i am working on an app.
The below code is a simple react-native app which has a custom component with custom events.
But the problem is the variable state is not updated on the first click on the component. But when i click on the second item, The state of the variable is updated.
Please find the code and screenshot below.
App.js
import React, {useState} from 'react';
import { Text, SafeAreaView, ToastAndroid } from 'react-native';
import Dropdown from './components/dropdown';
const app = () => {
const [ itemData, setItemData ] = useState('');
return (
<SafeAreaView style={{ margin: 50 }}>
<Dropdown
onPressItems={(item) => {
ToastAndroid.show('item: ' + item, ToastAndroid.LONG)
setItemData(item)
ToastAndroid.show('setItem: ' + itemData, ToastAndroid.LONG)
}}/>
</SafeAreaView>
);
}
export default app;
Dropdown.js
import React, { useState } from 'react';
import { TouchableOpacity, Text } from 'react-native';
const Dropdown = (props) => {
return (
<TouchableOpacity onPress={() => { props.onPressItems('this is sample data') }}>
<Text>Sample Text</Text>
</TouchableOpacity>
);
}
export default Dropdown;
Screenshot
Code: https://snack.expo.dev/#likithsai/custom-component
Please help me on this issue. Thanks.
useState() hook changes the state asynchronously. so you can't make sure that the state will be changed immediately after calling setItemData() function.
Try useEffect to run a side effect whenever the state changes.
useEffect(() => {
ToastAndroid.show("setItem: " + itemData, ToastAndroid.LONG);
}, [itemData]);
However, this code will show the toast on the component mount. to prevent it try something like this:
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
ToastAndroid.show("setItem: " + itemData, ToastAndroid.LONG);
}
}, [itemData]);
Your code is working just as expected. One of the hooks that are useful to watch for state updates is useEffect. You can add this to your code and see it's working properly:
const app = () => {
const [ itemData, setItemData ] = useState('');
React.useEffect(() => {
console.log('updated itemData:', itemData)
}, [itemData])
return (
<SafeAreaView style={{ margin: 50 }}>
<Dropdown
onPressItems={(item) => {
ToastAndroid.show('item: ' + item, ToastAndroid.LONG)
setItemData(item)
ToastAndroid.show('setItem: ' + itemData, ToastAndroid.LONG)
}}/>
</SafeAreaView>
);
}
You need to take into consideration that useState updates are asynchronous, which means the change won't be reflected immediately.

ReferenceError: Can't find variable: props

In React-Native, I am creating a functional component called ImageSelector, in which I am using expo-image-picker and using the image URI as a required field in a parent component using Formik. My simulator works and I am able to successfully pick a generic image and log the image URI in the console ('success: ' + result.uri) but here are the following errors:
I want to display the image in the image component below but the image does not display (it does not break). I get the following error Unhandled promise rejection: ReferenceError: Can't find variable: props which I suppose is referring to the parent form component but I do not know what to change to get this error to go away.
Parent Component
import { View, Text, Button } from 'react-native';
import { Formik } from 'formik';
import * as yup from 'yup';
import ImageSelector from '../shared/imagePicker';
const newPostSchema = yup.object({
image: yup.string()
.required(),
})
export default function CreatePost({navigation}) {
const setImageURI = (image) => {
props.setFieldValue('imageUri', image.uri)
}
return (
<View style={styles?.container}>
<Formik
initialValues={{
imageURI: null,
}}
validationSchema={newPostSchema}
onSubmit={(values, actions) => {
console.log(values);
navigation.navigate('ReviewPost', {
imageURI: values.imageURI,
});
}}
>
{props => (
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
)}
</Formik>
</View>
)
}
*** Nested ImageSelector Component in another file ***
import React, {useState, useEffect} from 'react';
import {View, Button, Image, StyleSheet} from 'react-native';
import * as ImagePicker from 'expo-image-picker';
const ImageSelector = ({image, onImagePicked}) => {
const [selectedImage, setSelectedImage] = useState();
useEffect(() => {
if(image) {
console.log('useEffect: ' + image);
setSelectedImage({uri: image});
}
}, [image])
pickImageHandler = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
title: 'Choose Image',
maxWidth: 800,
maxHeight: 600,
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1
});
if (!result.cancelled) {
console.log('success: ' + result.uri);
onImagePicked({uri: result.uri});
console.log('a');
setSelectedImage({uri: result.uri});
console.log('b');
}
if (result.cancelled) {
console.log('result cancelled: ' + result.cancelled);
}
}
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<Image source={selectedImage} />
</View>
<View style={styles.button}>
<Button title='Pick Image' onPress={this.pickImageHandler} />
</View>
</View>
)
}
The following 4 lines do not execute (console logs are for testing to ensure they don't get called):
onImagePicked({uri: result.uri});*
console.log('a'); *
setSelectedImage({uri: result.uri});*
console.log('b');*
I need to get the props-related error to go away, set selectedImage to equal result.uri, and have the image display in the <Image /> component using selectedImage.uri as the image source.
Help?
The problem here is in the error message. Since you are creating a functional component called CreatePost, the typical syntax for passing props would be
export default function CreatePost(props) {
...
}
So your component can access the props that are passed down to it, such as setFieldValue, however, you have used the spread operator {navigation} instead of props, so you are already extracting all the props when you do that. Thus, the scope does not know of any props variable. So, for now, I would try changing the argument to this
export default function CreatePost(props) {
const { navigation } = props;
...
}
That way wherever else in the scope you have referenced props will still work and you will not lose access to the navigation property either, alternatively, you can simply change 'navigation.navigate' to 'props.navigation.navigate' also. So javascript is saying cant find variable props, because to it, this is just a simple vanilla javascript function, and it does not intuitively know of a variable called props, you have to explicitly call it that.
Also, I feel like there might still be issues in this part of the code
{props => (
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
)}
So it would help if you could post the code where you are using your component, to see what props, such as setFieldValue, navigation etc.you are passing.
You can just rewrite that part as
<Formik
initialValues={{
imageURI: null,
}}
validationSchema={newPostSchema}
onSubmit={(values, actions) => {
console.log(values);
navigation.navigate('ReviewPost', {
imageURI: values.imageURI,
});
}}
>
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
Without doing the {props => part as with the refactor now you already have access to props in the scope.

mount a component only once and not unmount it again

Perhaps what I think can solve my issue is not the right one. Happy to hearing ideas. I am getting:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and async task in a useEffect cleanup function
and tracked it down to one component that is in my headerRight portion of the status bar. I was under the impression it mounts only once. Regardless, the component talks to a syncing process that happens and updates the state. For each status of the sycing, a different icon is displayed.
dataOperations is a NativeModules class that talks to some JAVA that does the background syncing and sends the status to RN.
import React, {useState, useEffect} from 'react';
import {DeviceEventEmitter } from 'react-native';
import DataOperations from "../../../../lib/databaseOperations"
const CommStatus: () => React$Node = () => {
let [status, updateStatus] = useState('');
const db = new DataOperations();
const onCommStatus = (event) => {
status = event['status'];
updateStatus(status);
};
const startSyncing = () => {
db.startSyncing();
};
const listner = DeviceEventEmitter.addListener(
'syncStatusChanged',
onCommStatus,
);
//NOT SURE THIS AS AN EFFECT
const removeListner = () =>{
DeviceEventEmitter.removeListener(listner)
}
//REMOVING THIS useEffect hides the error
useEffect(() => {
startSyncing();
return ()=>removeListner(); // just added this to try
}, []);
//TODO: find icons for stopped and idle. And perhaps animate BUSY?
const renderIcon = (status) => {
//STOPPED and IDLE are same here.
if (status == 'BUSY') {
return (
<Icon
name="trending-down"
/>
);
} else if (status == 'IS_CONNECTING') {
...another icon
}
};
renderIcon();
return <>{renderIcon(status)}</>;
};
export default CommStatus;
The component is loaded as part of the stack navigation as follows:
headerRight: () => (
<>
<CommStatus/>
</>
),
you can use App.js for that.
<Provider store={store}>
<ParentView>
<View style={{ flex: 1 }}>
<AppNavigator />
<AppToast />
</View>
</ParentView>
</Provider>
so in this case will mount only once.

NavigationEvents is not working when use going back

I am building a small sound player page. I am using expo-av library.
I got noticed when the user going forward {NavigationEvents onWillBlur } is working and when he goes backward it's not executing.
What I need to reach are :
1) Stop sound playing when the user leave page either backward or forward.
2) If user presses play twice the sound is being played twice so I don't want it to be played again if it's already running
If there is any other library could be use instead of expo-av ?
import React, {useState} from 'react';
import {View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native';
import { NavigationEvents } from 'react-navigation';
import { Audio } from 'expo-av';
import {AntDesign, Entypo} from '#expo/vector-icons';
const PlaySound = ({link}) => {
const [error, setError] = useState('')
const soundObject = new Audio.Sound();
const mySound = async () => {
try {
await soundObject.loadAsync({ uri : link });
await soundObject.playAsync();
} catch (err) {
setError('Wait while uploading your sound');
}
}
const stopSound = async () => {
try {
await soundObject.stopAsync(mySound);
} catch (error) {
setError('You must Play Sound First')
}
}
const pause = async () => {
try {
await soundObject.pauseAsync(mySound);
} catch (error) {
setError('Something went wrong !!! Please try again');
}
}
return (
<View>
<NavigationEvents onWillBlur = {stopSound} />
<Text>Play Sound</Text>
<View style = {styles.row}>
<TouchableOpacity
onPress = {mySound}>
<AntDesign name = 'caretright' size = {25} />
</TouchableOpacity>
<TouchableOpacity
onPress = {stopSound} >
<Entypo name = 'controller-stop' size = {25}/>
</TouchableOpacity>
<TouchableOpacity
onPress = {pause}>
<AntDesign name = 'pause' size = {25} />
</TouchableOpacity>
</View>
{error ? <Text>{error} </Text> : null }
</View>
);
};
const styles = StyleSheet.create({
row : {
flexDirection : 'row',
justifyContent : 'space-between',
marginVertical : 10
}
});
export default PlaySound;
For the problem 1 in which you have to stop player when user leaves the page. You can use useEffect hook. It will be something like that,
useEffect(() => {
return () => {
stopSound();
}
}, []);
So in the above useEffect hook, the returned function will run when component will unmount from screen (forward or backward).
For the 2nd problem, you have to disable play button to avoid multiple clicks. You can create a state using useState hook and make it false on Play button click and pass this playButtonState to disable prop of Play Button Touchable Opacity.
I hope it's clear to you now.

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;