UseAsyncstorage causes infinit loop in react native - reactjs

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

Related

how to display a state which is passed as an object to another screen in react native

So I have created a state like :
const [inState, setInState] = useState([<View />]);
Then on click of some buttons, I am updating inState
const breakOutClick = () => {
setInState([
...inState,
<>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status={'Break-Out'}
time={time}
/>
</>,
]);
};
const breakInClick = () => {
setInState([
...inState,
<>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status={'Break-In'}
time={time}
/>
</>,
]);
};
I am able to display everything stored in inState, on this same screen in this manner:
<View>
{inState}
</View>
I am passing this inState to another screen in the following manner:
props.navigation.navigate(ChartScreen, {
inState: Object.assign({}, inState),
});
Then on this second screen, i.e, ChartSCreen, I did the following:
const ChartScreen = (props: any) => {
const {inState} = props.route.params;
useEffect(() => {
console.log('>>>>>>>>>', inState);
}, []);
return (
<View>
{inState} // getting error here
</View>
);
};
I have console the inState, which looks like this:
{
"0": <ForwardRef />,
"1": <React.Fragment>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status="Break-In"
time="17:51:40"
/>
</React.Fragment>,
"2": <React.Fragment>
<StatusBoxComponent
ImageConfigIconCheckOut={true}
status="Break-Out"
time="17:51:42"
/>
</React.Fragment>
}
How can I display the multiple StatusBoxComponent on my second screen?
You are displaying initially an array, but by calling Object.assign({}, inState) you're creating an object. Where you're getting the error, you're attempting to render that object, not an array of components. Try using Object.values to get only the values and virtually "restore" your initial array.
const ChartScreen = (props: any) => {
const {inState} = props.route.params;
useEffect(() => {
console.log('>>>>>>>>>', inState);
}, []);
return (
<View>
{Object.values(inState)} // getting error here
</View>
);
};

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

Jest - Mock - toHaveBeenCalled() not working as expected?

Not understanding why the Mock function is not showing the tohaveBeenCalled when i am trying to mock in below fashion
//my App.js
import useAuth from "./src/hooks/useAuth";
import useBrunch from "./src/hooks/useBrunch";
import { someFunct } from "./functional";
export default function App() {
const { funct } = useBrunch();
return (
<>
<Max funct={funct} />
</>
);
}
function Max() {
const LoginSchema = object().shape({
emailId: string(),
password: string(),
});
const { funct } = useBrunch();
// const { funct, sas } = useBrunch();
// const [flag, setFlag] = React.useState(false);
//const { someFunct } = useAuth();
return (
<Formik
initialValues={{ emailId: "some#gmail.com" }}
onSubmit={(a, v) => {
funct(a);
}}
validationSchema={LoginSchema}
>
{({ handleSubmit, handleBlur, values, dirty, touched, errors }) => {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar testID="someId" style="auto" />
<CheckBox />
<TouchableOpacity
testID="masa"
disabled={false}
onPress={handleSubmit}
>
<View>
<Text testID="hello">SOmething</Text>
</View>
</TouchableOpacity>
<Button testID="something" title="someddd" onPress={() => {}} />
</View>
);
}}
</Formik>
);
}
export { Max };
//My hooks/useBrunch.js method
const useBrunch = () => {
console.log("called");
const funct = (a) => {
console.log("funct executed");
};
return { funct };
};
export default useBrunch;
//My App.test.js file
import React from "react";
import { render, act, waitFor, fireEvent } from "#testing-library/react-native";
import useBrunch from "./src/hooks/useBrunch";
import { Max } from "./App";
jest.mock("./src/hooks/useBrunch", () =>
jest.fn(() => ({
funct: jest.fn((a) => {
console.log(a, "called");
return a;
}),
}))
);
describe("Login something", () => {
it("first clss", async () => {
let { getByTestId, toJSON } = render(<Max />);
const something = getByTestId("masa");
await waitFor(() => {
fireEvent.press(something);
});
expect(useBrunch().funct).toHaveBeenCalled();
});
});
Expected- on simulation button bres the expected output should have a success
i have console inside mock function getting called and also shows paramter,
but cannot register that under toHaveBeenCalled() or similiar jest methods
Can anybody helpout here please?!
Try giving something like this:
const mockFunct = jest.fn((a) => {
console.log(a, "called");
return a;
})
jest.mock("./src/hooks/useBrunch", () =>
jest.fn(() => ({
funct: mockFunct
})
));
and then
expect(mockFunct).toHaveBeenCalled();

React navigation createStackNavigator switch between routes

I'm using React native with Expo and i'm trying to make a dynamic redirect navigation in my component.
When the user come to my page I want to check an async value and with the result of this value I want the user to be redirected to one of the Screen.
I'm using AppLoading from expo and redux state. But it's seems that I can't use navigate at this moment on my Stack.
Here is my code :
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
navigation.navigate('Page2')
} else {
navigation.navigate('Page1')
}
}
return (
appLoading ? (
<Stack.Navigator initialRouteName="Page1" headerMode="none">
<Stack.Screen name="Page1" component={Page1}/>
<Stack.Screen name="Page2" component={Page2}/>
</Stack.Navigator>
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Did I forgot something ?
I Implemented another solution with two different Stack navigation, but I don't know witch version is better to use ?
const Stack1 = createStackNavigator();
const Stack2 = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const [check, setCheck] = useState(false)
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
setCheck(goToPage2)
}
return (
appLoading ? (
check ? (
<Stack1.Navigator initialRouteName="Page1" headerMode="none">
<Stack1.Screen name="Page1" component={Page1Stack1}/>
<Stack1.Screen name="Page2" component={Page2Stack1}/>
</Stack1.Navigator>
) : (
<Stack2.Navigator initialRouteName="Page1" headerMode="none">
<Stack2.Screen name="Page1" component={Page1Stack2}/>
<Stack2.Screen name="Page2" component={Page2Stack2}/>
</Stack2.Navigator>
)
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Thanks for your help
You are right. You can't use navigation props at this stage.
Remove it from the component props and try doing it like this:
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const [initialRouteName, setInitialRouteName] = React.useState('');
const [loading, setLoading] = React.useState(true);
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
setInitialRouteName('Page2')
} else {
setInitialRouteName('Page1')
}
setLoading(false);
}
return (
loading === false && initialRouteName.length > 0 ? (
<Stack.Navigator initialRouteName={initialRouteName} headerMode="none">
<Stack.Screen name="Page1" component={Page1} />
<Stack.Screen name="Page2" component={Page2} />
</Stack.Navigator>
) : (
<AppLoading
startAsync={navigateToRoute}
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}

React Hook triggers re-render but React Navigation doesn't update screens

I wrote a useAuth hook. From this hook I use the var partnerId in my App.js to build the StackNavigator Screens. I expect, when I set/unset partnerId that the user gets automatically navigated to the Pairing or Dashboard component, because React Navigation will remove one of it. (As described here after the first code snippet)
So when I run the App, I see the dashboard, because I used addPartner('test');.
When I click the button on the dashboard, I see that the component re-rendered because the <Text> node changed from Partner to No Partner.
But I expect to get redirected to the Pairing component!
App.js
const AppStack = createStackNavigator();
const App = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
addPartner('test'); // for testing right now
return (
<NavigationContainer>
<AppStack.Navigator>
{!partnerId ? (
<AppStack.Screen
name="Pairing"
component={Pairing}
options={{headerShown: false}}
/>
) : (
<AppStack.Screen
name="Dashboard"
component={Dashboard}
options={{title: 'Dashboard'}}
/>
)}
</AppStack.Navigator>
</NavigationContainer>
);
};
export default App;
dashboard.js
const Dashboard = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
return (
<SafeAreaView>
<ScrollView>
<Text>Dashboard</Text>
{partnerId ? <Text>Partner</Text> : <Text>No Partner</Text>}
<Button
title="Remove Partner Id"
onPress={() => {
removePartner();
}}
/>
</ScrollView>
</SafeAreaView>
);
};
export default Dashboard;
useAuth.js
const PARTNERID_KEY = 'partnerId';
const MYID_KEY = 'myId';
const useAuth = () => {
const [myId, setMyId] = useState(null);
const [partnerId, setPartnerId] = useState(null);
const removePartner = async () => {
await AsyncStorage.removeItem(PARTNERID_KEY);
setPartnerId(null);
console.log('Removed Partner', partnerId);
};
const addPartner = async (newPartnerId) => {
if (!newPartnerId) {
console.error('newPartnerId is null/undefined');
return false;
}
if (newPartnerId.toUpperCase() === myId.toUpperCase()) {
console.error('newPartnerId and myId are equal');
return false;
}
await AsyncStorage.setItem(PARTNERID_KEY, newPartnerId);
setPartnerId(newPartnerId);
console.log('Added Partner', partnerId);
return true;
};
useEffect(() => {
const init = async () => {
// partner
const storagePartnerId = await AsyncStorage.getItem(PARTNERID_KEY);
if (storagePartnerId) {
setPartnerId(storagePartnerId);
}
// self
let storageMyId = await AsyncStorage.getItem(MYID_KEY);
if (!storageMyId) {
storageMyId = await UUIDGenerator.getRandomUUID();
await AsyncStorage.setItem(MYID_KEY, storageMyId);
}
setMyId(storageMyId);
console.log('Auth init', myId, partnerId);
};
init();
}, [myId, partnerId]);
return [myId, partnerId, addPartner, removePartner];
};
export default useAuth;
I found out that it's not possible to use a Hook like a global singleton (e.g. like a Angular Service), because it is not a singleton!
The answer is to call useAuth() once in App.js and use React.createContext() to pass the values to child components.
App.js
const AppStack = createStackNavigator();
export const AuthContext = React.createContext();
const App = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
const authContext = {myId, partnerId, addPartner, removePartner};
addPartner('test'); // testing
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<AppStack.Navigator>
{!partnerId ? (
<AppStack.Screen
name="Pairing"
component={Pairing}
options={{headerShown: false}}
/>
) : (
<AppStack.Screen
name="Dashboard"
component={Dashboard}
options={{title: 'Dashboard'}}
/>
)}
</AppStack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
};
export default App;
dashboard.js
const Dashboard = () => {
const foo = React.useContext(AuthContext);
return (
<SafeAreaView>
<ScrollView>
<Text>Dashboard</Text>
{foo.partnerId ? <Text>Partner</Text> : <Text>No Partner</Text>}
<Button
title="Remove Partner Id"
onPress={() => {
foo.removePartner();
}}
/>
</ScrollView>
</SafeAreaView>
);
};
export default Dashboard;

Resources