useState is not updating the State - react-native

I want to set some token value when user sign In and for that I've created a state by useState hook. But when i call the setter function the value is not updating.
App.js
const [userToken, setUserToken] = useState(false)
const authContext = useMemo(() => ({
signIn: () => {
setUserToken(true)
console.log('sign in called')
console.log('userToken: ',userToken)
setIsLoading(false)
},
signOut: () => {
setUserToken(false)
console.log('sign out called')
console.log('userToken: ',userToken)
setIsLoading(false)
},
signUp: () => {
setUserToken(true)
console.log('sign up called')
setIsLoading(false)
},
}))
Login.js
const { signIn } = useContext(AuthContext)
const handleSubmit = () => {
if (isValidEmail && isValidPassword) {
signIn()
}
}
<TouchableOpacity style={styles.signInCont} onPress={handleSubmit}>
<Text style={styles.signInTxt}>Sign In</Text>
</TouchableOpacity>
everytime userToken is 'false'.

The way I like to think about the useState modifier function is that it is asynch; i.e., the update is not immediate.
This is why you would probably want to look at a useEffect hook to handle the side effect of the state change; for instance:
const [userToken, setUserToken] = useState(false)
const authContext = useMemo(() => ({
signIn: () => {
setUserToken(true)
console.log('sign in called')
setIsLoading(false)
},
signOut: () => {
setUserToken(false)
console.log('sign out called')
setIsLoading(false)
},
signUp: () => {
setUserToken(true)
console.log('sign up called')
setIsLoading(false)
},
}))
useEffect(() => console.log('userToken: ', userToken), [userToken])
The above (if I've typed it right) will now print the state update, as a Side Effect of the state change; so for instance, if you are setting the userToken, then the time to fetch some data (say the user profile) would be in the side effect of the state change.
Please see: https://reactjs.org/docs/hooks-effect.html

Related

How to implement splash screen properly in a component which have hooks running?

Inside App.js I have auth validation (i am using useState, useMemo, useEffect) but when tried to impement splash screen and following Splas screen Dos I am getting Rendered more hooks than during the previous render. So following Rules of Hooks I put at top level useEffect and useState but now I am getting a new error Warning: 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 asynchronous tasks in %s.%s, a useEffect cleanup function, in App I see I need to cancel async functions but I need them to request the server and validate users.
This is how my code was before implementing Splash screen:
export default function App() {
const [auth, setAuth] = useState(undefined);
useEffect(() => {
(async () => {
const token = await getTokenApi();
if (token) {
setAuth({
token,
idUser: jwtDecode(token).id,
});
} else {
setAuth(null);
}
})();
}, []);
const login = (user) => {
setTokenApi(user.jwt);
setAuth({
token: user.jwt,
idUser: user.user.id,
});
};
const logout = () => {
if (auth) {
removeTokenApi();
setAuth(null);
}
};
const authData = useMemo(
() => ({
auth,
login,
logout,
}),
[auth]
);
if (auth === undefined) return null;
return (
<AuthContext.Provider value={authData}>
<PaperProvider>{auth ? <AppNavigation /> : <Auth />}</PaperProvider>
</AuthContext.Provider>
);
This is how i got it now
export default function App() {
const [auth, setAuth] = useState(undefined);
useEffect(() => {
(async () => {
const token = await getTokenApi();
if (token) {
setAuth({
token,
idUser: jwtDecode(token).id,
});
} else {
setAuth(null);
}
})();
}, []);
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await SplashScreen.preventAutoHideAsync();
await Font.loadAsync(Entypo.font);
await new Promise((resolve) => setTimeout(resolve, 4000));
} catch (e) {
console.warn(e);
} finally {
setAppIsReady(true);
}
}
prepare();
}, []);
const login = (user) => {
setTokenApi(user.jwt);
setAuth({
token: user.jwt,
idUser: user.user.id,
});
};
const logout = () => {
if (auth) {
removeTokenApi();
setAuth(null);
}
};
const authData = useMemo(
() => ({
auth,
login,
logout,
}),
[auth]
);
if (auth === undefined) return null;
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<View onLayout={onLayoutRootView}>
<AuthContext.Provider value={authData}>
<PaperProvider>{auth ? <AppNavigation /> : <Auth />}</PaperProvider>
</AuthContext.Provider>
</View>
);
}

How to use focus and blur listener in single useEffect react native

As you know in useEffect we return the unsubscribe at the end if we assign any listener to unsubscribe const as shown under
As we Using
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
return unsubscribe;
}, [navigation]);
As I want
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
const unsubscribe2 = navigation.addListener('blur', () => {
// code
})
// need to return both listeners
}, [navigation]);
You can cleanup like this
useEffect(() => {
navigation.addListener('focus', handler)
navigation.addListener('blur', handler)
return () => {
navigation.removeListener('focus', handler)
navigation.removeListener('blur', handler)
}
},[navigation])
The official example here https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup
I didn't test this, but you might be able to do something like this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
});
const unsubscribe2 = navigation.addListener('blur', () => {
// code
});
return () => {
// executed when unmount
unsubscribe();
unsubscribe2();
}
}, [navigation]);

how do I make an array in state - reactnative

I'm taking the images I uploaded to cloud storage, but the problem is the variable is not an array, so it is only storing just one url. How do I make variables with state array?
My code:
const reference = storage().ref('images');
const [imageUrl, setImageUrl] = useState();
const refer = storage().ref('images');
useEffect(() => {
try {
listFilesAndDirectories(reference).then(() => {
console.log('Finished listing');
});
refer.list().then(result => {
result.items.forEach(element => {
element.getDownloadURL().then(downloadUrl => {
setImageUrl(downloadUrl)
console.log(imageUrl)
console.log("=================")
}).catch(error =>{
alert(error)
})
})
})
} catch (error) {
alert(error);
}
}, []);
Is that what you are looking for?
const [items, setItems] = useState([]);
const handleStateChange = () => {
setItems(state => [...state, 'someNewItem']);
}
With useCallback
const handleStateChange = useCallback(function () {
setItems(state => [...state, 'someNewItem']);
}, [])

AppState removeEventListener not removing listener

Simple code:
useEffect(() => {
AppState.addEventListener("change", handleChangeEvent);
return (() => {
console.log("REMOVING EVENT LISTENER");
AppState.removeEventListener("change", handleChangeEvent);
});
}, []);
const handleChangeEvent = () => {
console.log("EVENT LISTENER FIRING");
};
Navigate to new screen:
const changeScreen = () => {
return props.navigation.navigate("MainView", {})
}
When arriving at new screen: The REMOVING EVENT LISTENER fires. But in this new screen, the event listener is still listening.
Why is it not being removed?
try using useFocussedEffect instead,
useFocusEffect(
React.useCallback(() => {
AppState.addEventListener("change", handleChangeEvent);
return () => {
AppState.removeEventListener("change", handleChangeEvent);
}
}, [])
);

How to test simulate on `react-navigation`'s navigate?

I am trying to make better codecoverage by simulating react-navigation's navigate function. Currently, I am having a problem in simulating react-navigation's navigate function.
I've tried to use mockStore from redux-mock-store.
const store = mockStore({
rehydrated: false,
});
const navigation = { navigate: jest.fn() };
const wrapper = shallow(<Login navigation={navigation} />);
Below I have a button (Note that this is a custom button).
<Button
constainerStyle={{ flex: 1 }}
onPress={() => this.goToSignup()}
style={styles.btnSignup}
textStyle={styles.txtSignUp}
>SIGNUP</Button>
goToSignup = () => {
this.props.navigation.navigate('Signup');
}
Below is my test code.
const wrapper = shallow(<Login navigation={navigation} />);
describe('interaction', () => {
beforeEach(() => {
wrapper.setProps({
navigation: {
navigate: jest.fn(),
},
});
});
describe('clicking the button', () => {
let goToSignupSpy;
let onLoginSpy;
let navigateSpy;
beforeEach(() => {
wrapper.instance().goToSignup = jest.fn();
wrapper.instance().onLogin = jest.fn();
goToSignupSpy = jest.spyOn(wrapper.instance(), 'goToSignup');
onLoginSpy = jest.spyOn(wrapper.instance(), 'onLogin');
navigateSpy = jest.spyOn(wrapper.instance().props.navigation, 'navigate');
});
it('should call onLogin callback', () => {
const loginBtn = wrapper.find(Button).at(1);
loginBtn.props().onPress();
expect(onLoginSpy).toBeCalled();
});
it('should call goToSignup callback', () => {
const signupBtn = wrapper.find(Button).at(0);
signupBtn.props().onPress();
expect(goToSignupSpy).toHaveBeenCalled();
/// failing
expect(navigateSpy).toBeCalledWith('Signup');
});
});
afterAll(() => {
Login.prototype.onLogin.mockRestore();
Login.prototype.goToSignup.mockRestore();
});
});
Everything works fine expect spy on react-navigation's navigate. What am I doing wrong?
Expect to simulate mock on react-navigation's navigate function.