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

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;
};
},[]);

Related

Why is setState not re-rendering the App?

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.

Centralized Error Handing with React-native and Redux

I'm doing centralized the Error Handing in react-native. I'm able to do this just showing the error message whenever error is thrown.
I've created a ErrorNotification added to the very top of the component hierarchy of the app.
const App = () =>
<ErrorNotification>
<Login />
</ErrorNotification>
//ErrorNotification
const ErrorNotification = ({ children, errorCode, errorShow }) => {
return errorShow || errorCode ? <Error {...{errorCode}} /> : children;
};
function mapStateToProps(state) {
return {
errorCode: state.errorReducer.errorCode,
errorShow: state.errorReducer.errorShow
};
}
export default connect(mapStateToProps)(ErrorNotification);
//Error
const Error = ({errorCode }) => {
const errorMes = getMessageByCode(errorCode)
return (
<View style={styles.container}>
<Text>{errorMes}</Text>
<TouchableOpacity onPress={() => reuqestedByUser()}>
<Text>Try again</Text>
</TouchableOpacity>
</View>
);
};
//Login
const Login = () => {
const reuqestedByUser = () => {
return null;
};
};
My question is... Is there any way to call the reuqestedByUser() of Login or any other component function from Error screen action.
Yes, since ErrorNotification is the parent of Login, It can pass reuqestedByUser to it as props.
Else, You can make use of the context API

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

activate async function on load screen

Im trying to create a users list with my api using a async function, but I dont know how to user it on load screen, can you help me
export default class Dermatologistas extends Component{
state ={
errorMessage: null,
users: []
}
getUserList = async () => {
try {
const response = await api.get('/auth/list');
const { users } = response.data
console.log(response.data)
this.setState({ users });
} catch (response) {
this.setState({ errorMessage: response.data.error });
}
};
render(){
const users = this.state.users
console.log(users)
return(
<View >
how you can see I was using a button to load everything, but i wanted to load when the screen loads
<Button onPress={this.getUserList} title='carregar'/>
{this.state.users.map(user => (
<View key={user._id} style={{marginTop: 15, alignItems: 'center'}}>
<Text>{user.title}</Text>
<Text>{user.speciality}</Text>
<Button title = 'View Profile'onPress ={() => this.props.navigation.navigate('Profile')}/>
</View>
))}
</View>
)
}
}
componentDidMount() {
this.getUserList();
}

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