Why is setState not re-rendering the App? - reactjs

I am trying to display a value stored in memory using a Text component in React Native. Storing and reading work fine, I can get proper values in console. I get values and setState with them.
getData = async (key) => {
try {
const value = await AsyncStorage.getItem("#"+key);
return value != null ? JSON.parse(value) : null;
} catch(e) {
// error screen
}
}
// ... some code here (btw its all inside of the class App)
fetchData = async () => {
this.setState({
role: await this.getData("role"),
calls_version: await this.getData("v_calls")
});
}
_ = this.fetchData(); // calling this to run fetchData function
splashScreen = () => {
return (
<View style={ styles.splash_screen }>
<Image style={ styles.splash_image } source={{ uri: "..." }}/>
<Text style={ styles.splash_text }>{ this.state.role }</Text>
</View>
);
}
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen options={{headerShown: false}} name="Splash" component={ this.splashScreen } />
</Stack.Navigator>
</NavigationContainer>
);
}
But when I run this, nothing happens.
If this can help, here is the sequence of this.state.role values:
undefined in render()
undefined in splashScreen()
"proper value" in render()
fetchData() completed
Thanks in advance.

Related

update state on unmonted component with context and hooks - react native

UPDATE: I've applied the instructor in this post, but even using the state isMounted and the useEffect cleanup function I still can't solve this problem. the code seems to work fine, but I always get this warning.
I have an app component that manages the navigation of two pages through conditional rendering, if I am logged in I enter one, if I am not I enter the other.
import {context} from "./components/context"
const Stack = createNativeStackNavigator();
export default function App() {
const [isLoggedIn, setLoggedIn] = useState(false);
useEffect(() => {
let isMounted = true;
let store = async () => {
await SecureStore.deleteItemAsync("accessToken")
let accessToken = await SecureStore.getItemAsync("accessToken");
if(accessToken && isMounted) {
setLoggedIn(true)
}
}
store().then()
return () => {
isMounted = false
}
}, [])
return (
<>
<NavigationContainer>
<context.Provider value={{isLoggedIn, setLoggedIn}}>
<Stack.Navigator >
<Stack.Screen name={isLoggedIn ? "HomePantry" : "Home"} component={isLoggedIn? HomePantry : Home} />
</Stack.Navigator>
</context.Provider>
</NavigationContainer>
</>
);
}
My file context.js:
export const context = React.createContext({});
This is my simple home component (before user login).
export default function Home({navigation}) {
return (
<View>
<Text> My pantry </Text>
<UserLogin />
</View>
);
}
This is the UserLogin child component. I am using the context to be able to update the isLoggedIn state once the user has entered their correct credentials. The problem is that the state is updated when the app component is unmounted and this causes no-op.
I get this warning:
"Can't perform a React state update on an unmounted component - memory leak?"
I haven't been able to resolve this situation yet if anyone has any ideas. thanks in advance.
import {context} from "./context";
export default function UserLogin() {
const contest = React.useContext(context)
return (
<View style={styles.inputsContainer}>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={
async (values, actions) => {
if(values.email.trim() !== "" && values.password.trim() !== ""){
const response = await fetch('https://lam21.iot-prism-lab.cs.unibo.it/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: values.email,
password: values.password
})
});
let json = await response.json()
if(json.accessToken){
contest.setLoggedIn(true)
await SecureStore.setItemAsync("accessToken", json.accessToken);
actions.resetForm({})
} else {
alert("Username o password sbagliati!")
}
}}}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.inputsContainer}>
<Text style={styles.labelText}> Email </Text>
<TextInput
required
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
placeholder={"Inserisci la tua mail.."}
style={styles.inputText}
/>
<Text style={styles.labelText}> Password </Text>
<TextInput
required
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
placeholder={"Inserisci la tua password.."}
style={styles.inputText}
/>
<View style={styles.inputButton}>
<Button onPress={handleSubmit} title="Submit" color="purple" style={styles.inputButton} />
</View>
</View>
)}
</Formik>
</View>
);
}
The homepantry component after the login:
export default function HomePantry() {
return (
<View>
<Text> My pantry </Text>
</View>
);
}
The problem is when you set a state on a promise. The component was mounted before the promise was resolved so you just need to check if it is still mounted;
useEffect(() => {
let isMounted = true;
let store = async () => {
let accessToken = await SecureStore.getItemAsync("accessToken");
if(accessToken && isMounted){
setLoggedIn(true)
}
}
store().then()
return () => {
isMounted = false;
};
},[]);

UseAsyncstorage causes infinit loop in react native

Problem is that my component content rendering in an infinite loop.
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
useAsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
useAsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return(
<View>
<DrawerHeader username = 'Danish hello' /> // Custom drawer header
<View style={styles.DrawerBody}>
<CustomDrawerLink name="Home" iconName='home' navigationScreen='HomeScreen' navigation={navigation} />
{
logged_in ?
<View>
<CustomDrawerLink name="Profile" iconName='user' /> // Drawer custom buttons
<CustomDrawerLink name="Cart" iconName='hamburger' />
</View>
: undefined
}
<Divider/>
{
logged_in ?
<TouchableOpacity style={{flexDirection:'row' , alignItems:'center'}} onPress={()=>{SignOut()}} >
<FontAwesome5 name='sign-out-alt' style={{fontSize:20, marginRight:10 , color:COLORS.red_color, width:35}} />
<Text style={{fontSize:16 , color:'gray'}}>Sign Out</Text>
</TouchableOpacity>
:
<View>
<CustomDrawerLink name="Sign In" iconName='sign-in-alt' navigationScreen='LoginScreen' navigation={navigation} />
<CustomDrawerLink name="Create New Account" iconName='user-plus' navigationScreen='RegisterScreen' navigation={navigation} />
</View>
}
</View>
</View>
)
}
Edited part
ADDED PARENT COMPONENT Here it is
As you mentioned i am using useEffect Hook in my parent component, Here is the code
here i am making Drawer (Side navigation bar)
const Drawer = createDrawerNavigator()
Here is App component
const App = () =>{
const network = useNetInfo()
const [Scren_navigation , setNavigation] = useState('')
const [activeDrawer , setActiveDrawer] = useState(<EmptyDrawer/>)
const UpdateScreen = (props) =>{
setNavigation(props.navigation)
return activeDrawer
}
useEffect(()=>{
setTimeout(() => {
network.isInternetReachable ? setActiveDrawer(<CustomDrawer navigation={Scren_navigation} />) : setActiveDrawer(<NoInternetDrawer navigation={Scren_navigation} />)
}, 5000);
})
return(
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => <UpdateScreen navigation={navigation} /> } >
<Drawer.Screen name="StackNavigation" component={Navigation} />
</Drawer.Navigator>
</NavigationContainer>
)
}
I have added parent Component, Please let me know where i was doing wrong things
The package provides two API's one with AsyncStorage and the other one is useAsyncStorage. Both have different usage patterns, you have mixed both in your snippet. Checkout the code below for example usage of each API.
AsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
AsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
AsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...;
}
UseAsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false);
const { getItem, setItem, removeItem } = useAsyncStorage('#storage_key');
useEffect(
() => {
getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...
}
The second problem is caused by the parent component because of a missing [] argument to the useEffect:
const App = () => {
const network = useNetInfo()
const [isRunningAvailabilityCheck, setIsRunningAvailabilityCheck] = useState(true);
const [internetIsAvailable, setInternetIsAvailable] = useState(true);
useEffect(() => {
setTimeout(() => {
setInternetIsAvailable(network.isInternetReachable);
setIsRunningAvailabilityCheck(false);
}, 5000);
}, []);
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => {
if(isRunningAvailabilityCheck){
return <EmptyDrawer/>;
}
if(internetIsAvailable){
return <CustomDrawer navigation={navigation} />
}
return <NoInternetDrawer navigation={navigation} />
}}>
<Drawer.Screen name="StackNavigation" component={Navigation}/>
</Drawer.Navigator>
</NavigationContainer>
)
}
Async storage Docs

Refresh screen or component when navigate to it

I have two screens, one for displaying the records consuming an API and the other for registering.
the problem is that when I do a register and navigate to the display screen it does not update.
This is a construction of the screen:
constructor(props) {
super(props);
this.state = {isLoading: true, pendIsLoading: true, dataSource: [], contentStorageS:""}
};
fetchDados = async () => {
let usuario = await AsyncStorage.getItem("ASCOFAR_app_usuario");
try {
const response = await api.get("api/listaRegistros.php?usuario="+usuario);
const responseData = await response.data
if(responseData.status == "ERRO"){
this.setState({
isLoading: false,
dataSource: "",
})
}else{
this.setState({
isLoading: false,
dataSource: responseData,
})
}
console.log(response)
} catch (error) {
Alert.alert(error)
}
}
async componentDidMount () {
this.fetchDados();
this.atualizaState();
}
tirarLoad() {
if(this.state.isLoading == true){
return (
<ActivityIndicator size="large" color="#be152c"/>
)
}else if(this.state.dataSource == ""){
return (
<ScrollView >
<View style={{justifyContent:"center", alignItems:"center",}}>
<Image
style ={{width:150, height:150, marginTop:35}}
source={require('../assets/images/aguardando.png')}
/>
</View>
</ScrollView>
)
}else{
return (
<ScrollView>
<Text style={styles.tituloGrid}>Formularios Enviados</Text>
{this.state.dataSource.map(dados => (
<View style={styles.list} key={dados.id}>
<Text style={styles.listChild}>{dados.id}</Text>
<Text style={styles.listChild}>{dados.nome}</Text>
<Text>|</Text>
<Text style={styles.listChild}>{dados.endereco}</Text>
</View>
))}
</ScrollView>
)
}
}
<View style={styles.grid}>
{this.tirarLoad()}
</View>
I need to know how to do when navigating to this screen to update API consumption
Assuming you are using React-Navigation, did you try to addListener
focus react-navigation documentation
You could also do it by componentDidUpdate. I could not find the official documentation for doing it on 5.x. I believe it still works with 5.x. (Doc on 3.x)
import { withNavigationFocus } from "react-navigation";
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
this.fetchDados()
//or other similar onFocus function
}
}
export default withNavigationFocus(TabScreen);
Try re-rendering your Home screen after navigation
this.props.navigation.navigate('Home', {
onBack: () => this.refresh() //function to refresh screen,
});
import { withNavigationFocus } from "react-navigation";
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.refreshFetch();
this.refreshLocal();
}
);
componentWillUnmount() {
this.willFocusSubscription.remove();
}

React Native Component will receive is deprecated

I'm creating a new react native app after writing some code I got this warning :
in the emulator.
But I don't see where the problem is.
This is my code - App.js:
const IS_ANDROID = Platform.OS === 'android';
const SLIDER_1_FIRST_ITEM = 1;
class App extends Component {
constructor (props) {
super(props);
this.state = {
slider1ActiveSlide: SLIDER_1_FIRST_ITEM
};
}
_renderItem ({item, index}) {
return <SliderEntry data={item} even={(index + 1) % 2 === 0} />;
}
_renderItemWithParallax ({item, index}, parallaxProps) {
return (
<SliderEntry
data={item}
even={(index + 1) % 2 === 0}
parallax={true}
parallaxProps={parallaxProps}
/>
);
}
_renderLightItem ({item, index}) {
return <SliderEntry data={item} even={false} />;
}
_renderDarkItem ({item, index}) {
return <SliderEntry data={item} even={true} />;
}
mainExample (number, title) {
const { slider1ActiveSlide } = this.state;
return (
<View style={styles.exampleContainer}>
<Carousel
ref={c => this._slider1Ref = c}
data={ENTRIES1}
renderItem={this._renderItemWithParallax}
sliderWidth={sliderWidth}
itemWidth={itemWidth}
hasParallaxImages={true}
firstItem={SLIDER_1_FIRST_ITEM}
inactiveSlideScale={0.94}
inactiveSlideOpacity={0.7}
// inactiveSlideShift={20}
containerCustomStyle={styles.slider}
contentContainerCustomStyle={styles.sliderContentContainer}
loop={true}
loopClonesPerSide={2}
autoplay={true}
autoplayDelay={4000}
autoplayInterval={3000}
onSnapToItem={(index) => this.setState({ slider1ActiveSlide: index }) }
/>
</View>
);
}
get gradient () {
return (
<LinearGradient
colors={[colors.background1, colors.background2]}
startPoint={{ x: 1, y: 0 }}
endPoint={{ x: 0, y: 1 }}
style={styles.gradient}
/>
);
}
render () {
const example1 = this.mainExample(1);
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<StatusBar
translucent={true}
backgroundColor={'rgba(0, 0, 0, 0.3)'}
barStyle={'light-content'}
/>
{ this.gradient }
<ScrollView
style={styles.scrollview}
scrollEventThrottle={200}
directionalLockEnabled={true}
>
{ example1 }
</ScrollView>
</View>
</SafeAreaView>
);
}
}
export default App;
All I used is this carousel library https://github.com/archriss/react-native-snap-carousel nothing else but I don't know what I am doing wrong in this case
and is it really the code isn't going to work in the future ?
As said by the warning, componentWillReceiveProps is deprecated.
The component react-native-snap-carousel use that feature and is deprecated in the latest version of react-native.
You have to either change the node_modules/react-native-snap-carousel to use componentDidUpdate, use another component for carousel or disable the warning until the carousel maintainers updates their package.
To disable it you can do, inside you App.js, in the constructor:
import {YellowBox} from 'react-native'; //import it
YellowBox.ignoreWarnings(['Warning: componentWillReceiveProps']);
Hope this helps you!
EDIT.
About the other Warning you've got. That happens when you do a setState when a component has already been unmounted. Make sure that recreates that scenario. I would suggest to not ignore this warning using YellowBox but solve it.

Button not displaying fetch results to the component?

I am creating a currency converter app and it will retrieve currency value from the API and multiply with the text input for the result. Both the API result and Text input are stored in State and passing as props to the Component
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency:'',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
<DisplayResult convert={this.state.pokeList} result={this.state.currency} />
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
DisplayResult
const DisplayResult =(props)=>{
const {convert,result} = props
console.log(convert);
return (
<View>
<Text>{result*convert}</Text>
</View>
)
}
export default DisplayResult;
I am trying to pass the API result and text input to the display component and this will multiply the values and will give the result.
Now this is not functioning or giving result
why this is not showing and where it's going wrong?
In your findCurrency method you just "call" the DisplayResult without returning it, but I don't think this is the good method to display your result.
You can use your component directly within the render method by testing your state variables, like this :
findCurrency = async () => {
try {
const pokemonApiCall = await fetch(
"https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}"
);
const pokemon = await pokemonApiCall.json();
this.setState({ pokeList: pokemon["KWD_INR"] }); // You set your "pokeList" variable up
} catch (err) {
console.log("Error fetching data-----------", err);
}
}
Note that you remove the DisplayResult call here and the function became an arrowed function, then in your render method use the test to make your result appear only if pokeList isn't empty :
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value={this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button title="Search" onPress={this.findCurrency.bind(this)} />
{this.state.pokeList !== "" && (
<DisplayResult
convert={this.state.pokeList}
result={this.state.currency}
/>
)}
</View>
);
}
Then, you don't have to bind your function in the onPress method like this, JavaScript immediately calls the function if you do this, instead, use arrow functions, you can access this by doing so in your function AND the onPress method doesn't call it if you don't click on the button, you just have to specify which function to execute when clicked :
<Button title="Search" onPress={this.findCurrency} />
If you have parameters in your function, use an arrow function instead :
<Button title="Search" onPress={() => yourFunction(param)} />
This should do the trick.
Try writing your function like that :
const findCurrency = async() => {
// ...
};
and call it like that
<Button
title="Search"
onPress={() => this.findCurrency()}
/>
I personnaly never use .bind because I think this is very unclear.
try using conditional rendering,
if data fetched, then only render.
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency: '',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
this.findCurrency.bind(this);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency()}
/>
</View>
{
if(this.state.pokeList !== '' || this.state.currency !== '') ?
<DisplayResult convert={this.state.pokeList} result={this.state.currency} /> : <div></div>
}
);
}
}

Resources