How to make the Formik call a function when the form is invalid - react-native

I have a login form, and when the user/password are not correct I call a function to make a animation in a component, but this function is only called when I pass the user/password to my api and it does not match with the information in the database. I want to call the function when the form is not valid too (before go to my api).
I'm using the formik lib to validate my form, and with that I show the message error, but I don't know how to make it call my animation function. My code is below:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { login } from '../../store/actions/user'
import {
View,
Text,
Animated,
Easing
} from 'react-native'
import { Formik } from 'formik'
import AuthContainer from '../../components/Auth/AuthContainer'
import AuthTitle from '../../components/Auth/AuthTitle'
import AuthButton from '../../components/Auth/AuthButton'
import AuthInput from '../../components/Auth/AuthInput'
import AuthHeader from '../../components/Auth/AuthHeader'
import SpeechBubble from '../../components/Auth/SpeechBubble'
import { validationSchema } from './validationSchema'
import styles from '../../assets/css/styles'
class Login extends Component {
state = {
email: '',
password: '',
shakeAnimation: new Animated.Value(0)
}
componentDidUpdate = prevProps => {
if(prevProps.isLoading && !this.props.isLoading && this.props.token){
this.props.navigation.navigate('Main')
}
else if(this.props.loginError && this.props.isLoading){
this.handleAnimation()
}
}
login = () => {
this.props.onLogin({ ...this.state })
}
navigateToRegister = () => {
this.props.navigation.navigate('Register')
}
handleAnimation = () => {
Animated.sequence([
Animated.timing(this.state.shakeAnimation, { toValue: 10, duration: 80, useNativeDriver: true }),
Animated.timing(this.state.shakeAnimation, { toValue: -10, duration: 80, useNativeDriver: true }),
Animated.timing(this.state.shakeAnimation, { toValue: 10, duration: 80, useNativeDriver: true }),
Animated.timing(this.state.shakeAnimation, { toValue: 0, duration: 80, useNativeDriver: true })
]).start()
}
render(){
return (
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values) => {
this.setState({ email: values.email, password: values.password })
this.login()
}}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnChange={false} >
{formikProps => (
<AuthContainer>
<AuthHeader />
<SpeechBubble>
<Animated.View
style={{ transform: [{translateX: this.state.shakeAnimation}] }}>
<AuthTitle
text={
formikProps.errors.email ?
formikProps.errors.email :
formikProps.errors.password ?
formikProps.errors.password :
!this.props.loginError ?
'Olá! Cadastrou sua conta usando e-mail e senha? Insira-os aqui:' :
this.props.loginError}
styleTitle={!this.props.loginError && formikProps.isValid ? styles.bodyText :
[styles.bodyText, styles.bodyTextError]}/>
</Animated.View>
<AuthInput
placeholder='Seu e-mail'
keyboardType='email-address'
autoFocus={true}
autoCorrect={false}
autoCapitalize='none'
returnKeyType='next'
value={formikProps.values.email}
onChangeText={formikProps.handleChange('email')}
onBlur={formikProps.handleBlur('email')}
blurOnSubmit={false} />
<AuthInput
placeholder='Sua senha'
secureTextEntry={true}
returnKeyType='go'
value={formikProps.values.password}
onBlur={formikProps.handleBlur('password')}
onChangeText={formikProps.handleChange('password')} />
<AuthButton title='Entrar' onPress={formikProps.handleSubmit} />
<View style={styles.bodyBottom}>
<Text style={styles.bodyLink}>Esqueci minha senha</Text>
<Text style={styles.bodyLink} onPress={this.navigateToRegister}>
Ainda não cadastrei uma conta
</Text>
</View>
</SpeechBubble>
</AuthContainer>
)}
</Formik>
)
}
}
const mapStateToProps = ({ user }) => {
return {
isLoading: user.isLoading,
token: user.token,
loginError: user.loginError,
}
}
const mapDispatchToProps = dispatch => {
return {
onLogin: user => dispatch(login(user))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)

The place where you pass formikProps.handleSubmit, just pass a function that calls you api and after calls formikProps.handleSubmit
<AuthButton
title='Entrar'
onPress={() => {
// call what you want
// call formik handleSubmit after
formikProps.handleSubmit()
}}
/>

Related

RTK Query Error: 'AbortError: Aborted' while running test

I'm trying to run a test on login form submit which goes through a Rest API call to authenticate a user.
I have configured MSW for mocking the rest API. Whenever I am running the npm test command the rest api call isn't going through and returning an error. Seems like the mock API isn't working in this case
I have configured MSW for mocking the rest API. I am attaching the handler file, jest setup file and screen file below for reference.
jest.setup.config
import mockAsyncStorage from '#react-native-async-storage/async-storage/jest/async-storage-mock';
import 'whatwg-fetch';
global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
jest.mock('#react-native-async-storage/async-storage', () => mockAsyncStorage);
login.screen.tsx
import React from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { Button, Pressable, SafeAreaView, Text, View } from 'react-native';
import styles from './EmailLogin.style';
import Input from '../../components/Input/Input.component';
import { color } from '../../theme';
import LinearGradient from 'react-native-linear-gradient';
import { setUser } from '../../stores/user.reducer';
import { useLoginUserMutation } from '../../services/api/auth';
import { useAppDispatch } from '../../hooks/redux';
const EMAIL_PATTERN = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i;
interface IEmailFormInputs {
email: string;
password: string;
}
const EmailLogin: React.FC = ({ navigation }: any) => {
const dispatch = useAppDispatch();
const [loginUser, { isLoading, isError, error, isSuccess }] =
useLoginUserMutation();
const onSubmit: SubmitHandler<IEmailFormInputs> = async requestData => {
try {
const { result } = await loginUser(requestData).unwrap();
dispatch(setUser(result));
navigation.navigate('Home');
} catch (err) {
console.log(err);
}
};
const {
control,
handleSubmit,
formState: { errors },
} = useForm<IEmailFormInputs>({
mode: 'onChange',
defaultValues: {
email: '',
password: '',
},
});
return (
<SafeAreaView style={styles.whiteBackground}>
<View style={styles.mainContainer}>
<View style={styles.welcomeTextContainer}>
<Text style={styles.welcomeTextTitle}>HELLO,</Text>
<Text style={styles.welcomeTextSubTitle}>Welcome back</Text>
</View>
<View style={styles.inputContainer}>
<Input
name="email"
control={control}
rules={{
pattern: {
value: EMAIL_PATTERN,
message: 'Invalid email address',
},
}}
error={errors.email}
placeholder={'Email'}
autoCapitalize={'none'}
autoCorrect={false}
/>
</View>
<View style={styles.inputContainer}>
<Input
name="password"
control={control}
rules={{
minLength: {
value: 3,
message: 'Password should be minimum 3 characters long',
},
}}
secureTextEntry={true}
error={errors.password}
placeholder={'Password'}
autoCapitalize={'none'}
autoCorrect={false}
/>
</View>
<Pressable onPress={handleSubmit(onSubmit)}>
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={color.gradient.primary}
style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</LinearGradient>
</Pressable>
</View>
</SafeAreaView>
);
};
export default EmailLogin;
login.spec.js
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '../../test-utils';
import { server } from '../../mocks/server';
import EmailLogin from './EmailLogin.screen';
describe('Home', () => {
//handles valid input submission
it('handle valid form submission', async () => {
await render(<EmailLogin />);
await act(async () => {
fireEvent(
screen.getByPlaceholderText('Email'),
'onChangeText',
'sample#email.com',
);
fireEvent(
screen.getByPlaceholderText('Password'),
'onChangeText',
'password',
);
fireEvent.press(screen.getByText('Login'));
//expected result on success login to be mentioned here
});
});
});
mocks/handler.js
import { rest } from 'msw';
import config from '../config';
export const handlers = [
rest.post(`*url*/user/auth`, (req, res, ctx) => {
console.log(req);
// successful response
return res(
ctx.status(200),
ctx.json({
success: true,
result: {
auth_token: ['authtoken'],
email: req.bodyUsed.email,
id: 1,
first_name: 'xxx',
last_name: 'xxx',
number: 'xxxxxxx',
user_state: 1,
phone_verified: true,
},
}),
);
}),
];
I get the following error when
console.log
{ status: 'FETCH_ERROR', error: 'AbortError: Aborted' }
at src/screens/EmailLogin/EmailLogin.screen.tsx:32:15
at Generator.throw (<anonymous>)

losing my mind - why doesn't my fetch re render (redux)

In my project I have many users and many resources (and many user_resources/the join between users and resources).When I POST to user_resources I see it work on my rails backend (as in I see that instance posted) but in my react native front end I don't see it listed upon update. However, once the app is completely refresh (when I stop and restart the expo server), I finally see those items rendered. ANY IDEAS? I've been working on this forever now to no avail and my project is due tmrw, so any help is appreciated.
screen where I post to user_resources:
import React from 'react';
import { ScrollView,SafeAreaView,StyleSheet, Text, View, FlatList, TouchableOpacity,Button, NativeEventEmitter} from 'react-native';
import {connect} from 'react-redux';
import {fetchResources,searchChanged} from '../actions';
import { addUserResource } from '../actions'
import {SearchBar} from 'react-native-elements';
import { MaterialIcons } from '#expo/vector-icons';
import { MaterialCommunityIcons } from '#expo/vector-icons';
class ResourcesScreen extends React.Component {
state = {
search: ''
}
componentDidMount = () =>{
this.props.fetchResources();
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: "100%",
backgroundColor: "lightblue",
}}
/>
);
}
handlePress(item) {
debugger
fetch('http://localhost:3000/user_resources', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
resource_id: item.id,
user_id: this.props.users.id,
name: item.name,
description:item.description,
link:item.link,
})
})
.then(res => res.json())
.then(data2 => {
console.log(data2)
this.props.addUserResource(data2)
console.log(this.props)
})
}
header = () => {
return <View>
<Text style={styles.header}>Resources</Text>
</View>
}
onSearchChange = text => {
this.setState({search:text})
}
render(){
return(
<SafeAreaView>
<SearchBar placeholderTextColor="white" placeholder="Enter resource name here" onChangeText={this.onSearchChange} value={this.state.search}/>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Add A New Resource',{topicId:this.props.route.params.topicId})} style={styles.buttonitem}>
<Text style={styles.text}>
<MaterialIcons name="add-circle-outline" size={24} color="white"/>Add A New Resource
</Text>
</TouchableOpacity>
<FlatList keyExtractor={(item)=> item.id.toString()} data={this.props.resourceName} ItemSeparatorComponent = { this.FlatListItemSeparator } renderItem={({item}) => {
return <TouchableOpacity style={styles.material2}>
<Text onPress={() => this.props.navigation.navigate('Add A New Resource',{topicId:item.id})} style={styles.listitem}>{item.name}</Text>
<MaterialCommunityIcons name="bookmark-plus" size={50} color="#16a085" backgroundColor='black' onPress={()=>this.handlePress(item)}/>
</TouchableOpacity>
}}
ListHeaderComponent = {this.header}/>
</SafeAreaView>
)
}
}
const mapStateToProps = (state) => {
return {
resourceName: state.resourceReducer.resources,
users: state.userReducer,
search:state.resourceReducer.searchTerm
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchResources: () => dispatch(fetchResources()),
addUserResource,
searchChanged
}
}
export default connect(mapStateToProps,mapDispatchToProps)(ResourcesScreen)
After this I head to the profile page where the user_resources SHOULD be displayed, but aren't
import React from 'react';
import { ScrollView,StyleSheet, Text, View, FlatList, TouchableOpacity} from 'react-native';
import {connect} from 'react-redux';
import {SearchBar} from 'react-native-elements';
import { AntDesign } from '#expo/vector-icons';
class Profile extends React.Component{
handleDelete = (id) => {
debugger
fetch(`http://localhost:3000/user_resources/${id}`, {
method: "DELETE",
headers: {
"Authorization": this.props.users.token
}
})
.then(r => r.json())
.then((delResource) => {
console.log(delResource)
this.props.deleteOneFood(delResource)
console.log('deleted')
this.forceUpdate()
})
}
render(){
return(
<View>
{this.props.users.user_resources.map(singleResource=> {
return <Text key={singleResource.id}>{singleResource.name}</Text>
})}
</View>
)}
}
let deleteOneResource = (id) => {
return {
type: "DELETE_ONE_USER_RESOURCE",
payload: id
}
}
const mapDispatchToProps = {
deleteOneResource
}
const mapStateToProps = (state) => {
return {
users: state.userReducer,
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Profile)
I had a flatlist before but thought that may be causing the issues so rendered it another way, still no luck. I tried forceUpdates as well, still no luck. I'm not sure if the issue is coming from my reducer:
let userInitialState = {
id: 0,
username: "",
name: '',
category: '',
token: "",
user_resources:[],
}
let userReducer = (state = userInitialState, action) => {
switch(action.type){
case "ADD_USERS":
let singleNestedObject = {
...action.users.user,
token: action.users.token
}
return {
...state,
username: action.users.user.username,
token: action.users.token,
id: action.users.user.id,
name: action.users.user.name,
category: action.users.user.category,
user_resources: action.users.user.user_resources
}
case "ADD_ONE_USER_RESOURCE":
let copyOfResources = [...state.user_resources, action.userResources]
return {
...state,
user_resources: copyOfResources
}
default:
return state
}
}
and it's action
export const addUserResource = (resourceInfo) => {
return {
type: "ADD_ONE_USER_RESOURCE",
userResources: resourceInfo
}
}
Please help me find the issue here, I'm losing it.

Undefined is not an object (evaluating '_this2.setState')

I'm new on react-native development and i try to add a login / register to my application, my login by email and by facebook is working fine (i'm in Firebase database, so it's ok). I want to show another view after the login of the user. Here is my firebaseconfig (imported in my login) :
export async function loginUser(email, password) {
try {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(response => {
this.setState({ authentificated: true });
});
} catch (error) {
console.log(error.toString);
}
}
export async function facebookLogin() {
Facebook.initializeAsync("928193664206969");
const { type, token } = await Facebook.logInWithReadPermissionsAsync(
"928193664206969",
{
permissions: ["public_profile"]
}
);
if (type == "success") {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
firebase
.auth()
.signInWithCredential(credential)
.then(response => {
this.setState({ authentificated: true });
})
.catch(error => {
console.log(error);
});
}
}
Here is my Login page :
import React from "react";
import { View, Text } from "react-native";
import EmailLoging from "../components/EmailLoging";
import { Welcome } from "../pages/Welcome";
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
authentificated: false
};
}
renderCurrentState() {
if (this.state.authentificated) {
return <Welcome />;
} else {
return <EmailLoging />;
}
}
render() {
return this.renderCurrentState();
}
}
In the application, when i click on login with facebook, it's work, then i go back to the login screen with this error :
undefined is not an object (evaluating '_this2.setState')
- node_modules\#firebase\auth\dist\auth.js:16:1054 in cb.prototype.toString
- node_modules\#firebase\auth\dist\auth.js:19:232 in <anonymous>
- node_modules\#firebase\auth\dist\auth.js:19:167 in xb
- node_modules\#firebase\auth\dist\auth.js:19:0 in wb
- node_modules\#firebase\auth\dist\auth.js:13:280 in <anonymous>
- node_modules\promise\setimmediate\core.js:37:14 in tryCallOne
- node_modules\promise\setimmediate\core.js:123:25 in setImmediate$argument_0
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:146:14 in _callTimer
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:194:17 in _callImmediatesPass
- node_modules\react-native\Libraries\Core\Timers\JSTimers.js:458:30 in callImmediates
* [native code]:null in callImmediates
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:407:6 in __callImmediates
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:143:6 in __guard$argument_0
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:384:10 in __guard
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:142:17 in __guard$argument_0
* [native code]:null in flushedQueue
* [native code]:null in invokeCallbackAndReturnFlushedQueue
I have done some search on Stackoverflow, and i found this : TypeError: undefined is not an object (evaluating 'this.setState') but i have only arrow function so it's not the problem..
Thank you
Update :
Now i don't have the error, but the screen didn't change after the login, my code, login functions : (firebaseConfig.js)
export async function loginUser(email, password) {
try {
firebase.auth().signInWithEmailAndPassword(email, password);
} catch (error) {
console.log(error.toString);
}
}
export async function facebookLogin() {
Facebook.initializeAsync("928193664206969");
const { type, token } = await Facebook.logInWithReadPermissionsAsync(
"928193664206969",
{
permissions: ["public_profile"]
}
);
if (type == "success") {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
firebase
.auth()
.signInWithCredential(credential)
.catch(error => {
console.log(error);
});
}
}
Login.js :
import React from "react";
import { View, Text } from "react-native";
import Welcome from "../pages/Welcome";
import Home from "../pages/Home";
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
authentificated: false
};
this.renderCurrentState = this.renderCurrentState.bind(this);
}
renderCurrentState = () => {
if (this.state.authentificated) {
return <Home />;
} else {
return <Welcome />;
}
};
render() {
return this.renderCurrentState();
}
}
Welcome.js :
import React from "react";
import { StyleSheet, Text, Image } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import logo from "../../assets/icon.png";
import { Button, Icon, Container } from "native-base";
import { Actions } from "react-native-router-flux";
import { facebookLogin } from "../components/firebaseConfig";
export default class Welcome extends React.Component {
render() {
return (
<LinearGradient
colors={["#004aaf", "#000"]}
style={{ flex: 1, alignItems: "center", justifyContent: "center" }}
>
<Container style={styles.backgroundImage}>
<Image source={logo} style={styles.logo}></Image>
<Text style={styles.loginText}>
─── Se connecter / S'inscrire avec ───
</Text>
<Button
light
full
rounded
style={{ padding: 10 }}
onPress={() => Actions.login()}
>
<Icon type="FontAwesome" name="envelope" />
<Text>Mon adresse email</Text>
</Button>
<Button
full
rounded
style={{ padding: 10, marginTop: 20 }}
onPress={() =>
facebookLogin().then(() => {
this.setState({ authentificated: true });
})
}
>
<Icon type="FontAwesome" name="facebook" />
<Text style={{ color: "#fff" }}>Mon compte Facebook</Text>
</Button>
</Container>
</LinearGradient>
);
}
}
const styles = StyleSheet.create({
backgroundImage: {
flex: 1,
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
padding: 25
},
logo: {
width: 170,
height: 130
},
loginText: {
paddingTop: 50,
paddingBottom: 50,
color: "#fff"
}
});
this.setState() should be called from inside the React component. You are wrongly calling it inside facebookLogin() and loginUser() functions.
You can fix it like this:
export async function loginUser(email, password) {
try {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
} catch (error) {
console.log(error.toString);
}
}
then in your component, you call
loginUser().then(() => {
this.setState({ authentificated: true });
});
But please make sure the method where you call, this is properly bound.
The common practice is to define methods like this:
renderCurrentState = () => {
}
Otherwise you would have to do the binding in component's constructor:
constructor(props) {
super(props);
// ...
this.renderCurrentState = this.renderCurrentState.bind(this);
}
What I have done is change loginUser() to return a promise. Then you use the function inside your component.
EDIT:
You can add a prop, eg. onLogin which you can use in Login component to update the authenticated state.
renderCurrentState() {
if (this.state.authentificated) {
return <Welcome />;
} else {
return <EmailLoging onLogin={this.updateState} />;
}
}
updateState = () => {
this.setState({ authentificated: true });
// possibly other code
}
You should call onLogin on loginUser().then(() => { ... })

How to navigate to another screen after axios action dispatch in reducer in react- native

I understand that this.props.isValidUser gets updated after action dispatches the axios promise. if the user is not valid is shows message. If the user is valid user, I want to navigate to another screen to enter pin. How do I navigate to another screen after I get axios result from action?
types.js
export const VALIDATE_USER = "VALIDATE_USER";
export const VALIDATE_PIN = "VALIDATE_PIN";
export const GET_ERRORS = "GET_ERRORS";
Reducer.js
import { VALIDATE_USER, VALIDATE_PIN, GET_ERRORS } from "../actions/types.js";
export default function (state = initialState, action) {
switch (action.type) {
case VALIDATE_USER:
return {
...state,
isValidUser: (action.payload == true) ? true : false,
Id: action.employeeId
};
case VALIDATE_PIN:
return {
...state,
isValidPin: action.payload,
action: "VALIDATE_PIN",
};
default:
return state;
}
}
action.js
import { GET_ERRORS, VALIDATE_USER, VALIDATE_PIN, } from "./types";
export const validateUser = (empId) => dispatch => {
axios.get(`${API}/api/Account/ValidateMobileAppUser?employeeId=${empId}`)
.then(res => {
dispatch({
type: VALIDATE_USER,
payload: res.data,
Id: empId,
});
})
.catch(err => {
dispatch({
type: VALIDATE_USER,
payload: false,
Id: empId
});
});
};
Login.js
import PropTypes from "prop-types";
import { validateUser } from "../actions/authActions";
class Login extends PureComponent {
constructor() {
super();
this.state = {
employeeId: "",
pin: "",
isValidUser: false,
};
this.onValidateUser = this.onValidateUser.bind(this);
this.onEmployeeId = this.onEmployeeId.bind(this);
}
onEmployeeId(employeeId) {
this.setState({ employeeId });
}
onValidateUser() {
this.props.validateUser(this.state.employeeId);
}
render() {
const { loading } = this.props.loading;
return (
<KeyboardAvoidingView style={styles.login} >
<ScrollView showsVerticalScrollIndicator={false}>
<Block padding={[10, theme.sizes.base * 2]} onPress={Keyboard.dismiss}>
<Block middle>
<Input
placeholder={this.state.placeholder}
keyboardType={this.state.keyboardType}
style={[styles.input]}
value={this.state.employeeId}
onChangeText={this.onEmployeeId}
/>
{(this.props.isValidUser == false) ? (
<Text center style={{ color: "#C00000", marginTop: 15, fontSize: 14 }}>
Employee Id not registered. Please contact HR.
</Text>
) : ""}
<Button
gradient
style={styles.loginButton}
onPress={this.onValidateUser}
>
<Text white center>
Login
</Text>
</Button>
</Block>
<Button
onPress={() => this.onGoToStep(1)}
style={{
borderWidth: 1,
borderRadius: 30,
borderColor: "#E46932"
}}
>
<Text gray caption center style={{ color: "#E46932" }}>
Don't have an account? Sign Up
</Text>
</Button>
</Block>
</ScrollView>
</KeyboardAvoidingView>
);
}
}
Login.propTypes = {
validateUser: PropTypes.func.isRequired,
errors: PropTypes.object.isRequired
};
function reducerCallback(state, ownProps) {
if (state.auth.isValidUser == true) {
ownProps.navigation.navigate("mPin", { Id: state.auth.employeeId, type: "LOGIN" });
}
}
const mapStateToProps = (state, ownProps) => ({
auth: reducerCallback(state, ownProps),
isValidUser: state.auth.isValidUser,
errors: state.errors
});
export default connect(
mapStateToProps,
{
validateUser,
}
)(Login);
this.props.isValidUser == false tells me if the user is valid or not. But if the user is valid I'm navigating to another screen using reducerCallback() function. I'm not aware if this is the correct way to do so. My question is how to I navigate to another screen after I get return result from async axios action and How to I set local state using setState when I get callback from axios dispatch. Please guide
Try to below code:
login.js:
onValidateUser() {
this.props.validateUser({
empId: this.state.employeeId,
onSuccess: () => {
//Navigate to other screen
},
onFailure: () => {
//Alert error message
},
});
}
Action.js:
export const validateUser = ({empId, onSuccess, onFailure}) => dispatch => {
axios
.get(
`${API}/api/Account/ValidateMobileAppUser?employeeId=${empId}`
)
.then(res => {
dispatch({
type: VALIDATE_USER,
payload: res.data,
Id: empId,
});
onSuccess();
})
.catch(err => {
dispatch({
type: VALIDATE_USER,
payload: false,
Id: empId
});
onFailure()
});
};

Only Android users getting this error on createuserwithemailandpassword

On iOS this has never been an issue, but a lot of my users are attempting to create a firebase user, then I write that newly created user's info in the realtime database. It's hit or miss, some users it works successfully, sometimes it takes more than one try. Let me add that I have only been on this project for a short time and I can already tell best practices are not being used. The Following is the code:
Using crashlytics, I am seeing the folllwing error:
Fatal Exception: com.facebook.react.common.JavascriptException
null is not an object (evaluating 't.navigator.dispatch'), stack: #364:2006 value#49:1280 #605:1154 value#49:1280 #590:497 value#49:1280 value#28:3311 #28:822 value#28:2565 value#28:794 value#-1
screens/login.js
import React, { Component } from 'react';
import { ... } from 'react-native';
import { connect } from 'react-redux';
import { authActions, ... } from '../redux/actions';
import firebase from 'react-native-firebase';
class Login extends Component {
static navigationOptions = () => ({
headerMode: 'none',
header: null,
});
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
forceCheckEmail: false,
forceCheckPassword: false,
};
}
componentWillReceiveProps(newProps) {
const { props } = this;
const {
error,
isBusy,
dismissError,
screenProps: {
modal: {
setContent,
clearContent,
setDismissHandler,
},
},
} = newProps;
if (props.error !== error || props.isBusy !== isBusy) {
const modalContent =
isBusy ? <Spinner text='One moment...' /> :
error ? <ErrorPopup message={error} /> :
null;
if (modalContent) {
setContent(modalContent, undefined, this.ref);
setDismissHandler(() => {
this.setState({ showForgotBlock: true })
dismissError();
});
} else {
clearContent();
}
}
}
handleLogin() {
Keyboard.dismiss();
this.props.login({
email: this.state.email,
password: this.state.password,
});
}
render() {
const {
keyboardIsVisible,
email,
password,
forceCheckEmail,
forceCheckPassword,
showForgotBlock,
} = this.state;
const {
...
navigation: {
navigate
}
} = this.props;
const emailValid = validateEmail(email);
const passwordValid = password.length > 5;
const loginEnabled = email !== '' && emailValid && passwordValid;
const forgotPasswordBlock = showForgotBlock ? (
<TouchableOpacity
onPress={() => restorePassword(email)}
style={{marginTop: -20, marginBottom: 10}}
>
<Text style={{color: '#777'}}>
Forgot your password?
</Text>
</TouchableOpacity>
): null;
firebase.analytics().setCurrentScreen('login', 'login');
return (
...
<TextInput
style={[styles.input, forceCheckEmail && !emailValid ? styles.failedInput : null]}
autoCorrect={false}
placeholder="Email"
onBlur={() => this.setState({ forceCheckEmail: true })}
autoCapitalize="none"
keyboardType="email-address"
placeholderTextColor={color.INPUT_TEXT}
onChangeText={email => this.setState({ email })}
value={email}
/>
<TextInput
style={[styles.input, forceCheckPassword && !passwordValid ? styles.failedInput : null]}
autoCorrect={false}
placeholder="Password"
onBlur={() => this.setState({ forceCheckPassword: true })}
placeholderTextColor={color.INPUT_TEXT}
secureTextEntry
onChangeText={password => this.setState({ password })}
value={password}
/>
...
<TouchableOpacity
style={[styles.button, styles.buttonPrimary]}
onPress={() => navigate('SignUp')}
>
<Text style={styles.buttonPrimaryText}>
SIGN UP
</Text>
</TouchableOpacity>
...
export default connect(
state => ({
...
}),
{
login: data => authActions.login(data),
...
},
)(Login);
actions/auth.js
import { createActions } from 'redux-feline-actions';// I question this dependency
import firebase from 'react-native-firebase';
import FBSDK from 'react-native-fbsdk';
const usersDB = firebase.database().ref('users');
const newUserData = {
point: 0,
savedNumbers: [],
};
export default createActions({
...
register: ({ name, email, phone, password }) => ({
useReducer: 'auth',
payload: firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(({user: { uid, email }}) => usersDB
.child(uid)
.set({
...newUserData,
name,
email,
phone,
id: uid,
})
.then(err => err || ({
...newUserData,
name,
email,
phone,
id: uid,
}))),
}),
...
stores/auth.js
import Immutable, { Map } from 'immutable';
import createAsyncStores from 'cat-stores'; // I also question this one
export default createAsyncStores({
auth: {
begin: state => state
.set('isBusy', true),
complete: (state, { payload }) => state
.set('isBusy', false)
.set('user', Immutable.fromJS(payload)),
error: {
default: (state, { payload }) => state
.set('error', payload.message)
.set('isBusy', false)
.set('user', null), // Android users keep getting this result I believe
},
},
...
},
Map({
isBusy: false,
error: null,
user: null,
redirectTo: null,
theme: Map(),
settings: Map(),
themeIsLoaded: false,
settingsAreLoaded: false,
}));
I expect the user to not have an issue with creating and saving new user info on Android, just like on iOS.