In my form, I'm trying to check email by using reactive-thunk to determine if the email address was already received. Everything is working properly, except for one thing. I request the api and I'm sending the data to the reducer, but the component I have access to the state is empty. Because the state value in the component is working before the reducer.
Is there any help of how to do that?
Submit.js
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
console.log(emailValidate); // fetch data in reducer(This data is empty because code run before reducer set state)
if (emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
const mapDispatchToProps = (dispatch) => {
validateEmail(email) {
dispatch(validateEmail(email));
},
};
};
const mapStateToProps = (state) => ({
emailValidate: state.registrationFormEmailValidate.data,
});
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.emailValidate.statusMessage !== prevProps.emailValidate.statusMessage) {
console.log(this.props.emailValidate);
if (this.props.emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
}
If you are using class component, you can use componentDidUpdate()
Related
I am using React Native and Redux. In initial state of Redux emailExists is set to null:
const INITIAL_STATE = {
// ...
emailExists: null,
};
when registering user. First, I check if user already exists by sending a request to server and if user already exists, I show a toast.
const registerUser = (values, actions) => {
checkEmail(values.userEmail); // takes time to get result of `emailExists`
if (emailExists) { // `emailExists` is now `null` couldn't wait for response
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail code look like this:
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
})
.catch((err) => {
console.log(`err`, err);
});
};
}
aftering dispatching dispatch(emailExists(res.data.exists));, the emailExists will be either true or false, but the problem is that since request takes time to get data from server, at first load of application emailExists is always set to null. Which means below condition will always be false in first load:
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
function emailExists(payload){
return {
type: userConstants.EMAIL_EXISTS,
emailExists: payload
}
}
How do I resolve this issue?
THANK YOU
You can modify functions like this to get the expected result.
function checkEmail(data,callback) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
// dispatch(emailExists(res.data.exists)); // You don't need this redux approach now because it will take time and will give you the same error
dispatch(authError("Email already exists"));
callback && callback(res.data.exists) // I am assuming here you got the response that email alreay exists and its value is true.
})
.catch((err) => {
console.log(`err`, err);
});
};
}
Now get the callback when the API request is done and you get the response.
const registerUser = (values, actions) => {
const callback = (isEmailExists) => {
//Here you will get the value of the checkemail API (true/false)
//Now do any action in this block of functions
if (isEmailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail(values.userEmail,callback);
}
Mantu's answer is one solution. I would prefer using async/await for this use case. dispatch() returns whatever the function returned by the action creator returns. So in this case you are returning a promise, meaning you can await your dispatch(checkEmail(values.userEmail)) call.
You will need to return whether the email exists from the promise, otherwise even if you weait for your checkEmail action to complete, the emailExists will not be up to date when you access it (since using useSelector will have to rerender the component to reflect the updates in the store).
const registerUser = async (values, actions) => {
const emailExists = await checkEmail(values.userEmail);
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
return res.data.exists;
})
.catch((err) => {
console.log(`err`, err);
});
};
}
If you don't want to return the property from the promise, you will need to get the value from the store synchronously via store.getState():
const registerUser = async (values, actions) => {
await checkEmail(values.userEmail);
const emailExists = selectEmailExists(store.getState());
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
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 a useEffect
cleanup function.
I have tried everything to fix but nothing works. I didn't even use "useEffect". This happens when I login to account and navigate the user to search page.
loginScreen.js
const validationSchema = Yup.object().shape({
email: Yup.string()
.label("Email")
.email("Enter a valid email")
.required("Please enter a registered email"),
password: Yup.string()
.label("Password")
.required()
.min(6, "Password must have at least 6 characters "),
});
const { width, height } = Dimensions.get("window");
class loginScreen extends React.Component {
state = {
passwordVisibility: true,
rightIcon: "ios-eye",
};
goToSignup = () => this.props.navigation.navigate("Signup");
handlePasswordVisibility = () => {
this.setState((prevState) => ({
rightIcon: prevState.rightIcon === "ios-eye" ? "ios-eye-off" : "ios-eye",
passwordVisibility: !prevState.passwordVisibility,
}));
};
handleOnLogin = async (values, actions) => {
const { email, password } = values;
try {
const response = await this.props.firebase.loginWithEmail(
email,
password
);
if (response.user) {
this.props.navigation.navigate("App");
}
} catch (error) {
alert("Seems like there is no account like that. Try something else.");
} finally {
actions.setSubmitting(false);
}
};
searchScreen.js
class searchScreen extends Component {
apiurl = "";
_isMounted = false;
constructor(props) {
super(props);
this.state = {
searchText: "",
results: [],
isLoading: true,
};
}
showMovie = async (imdbID) => {
await axios(this.apiurl + "&i=" + imdbID).then(({ data }) => {
let result = data;
this.props.navigation.navigate("Movie", {
selected: result,
movieID: imdbID,
});
});
};
componentDidMount() {
this._isMounted = true;
this.initial();
}
componentWillUnmount() {
this._isMounted = false;
}
initial = async () => {
const user = await this.props.firebase.getUser(user);
try {
await AsyncStorage.setItem("useruid", user.uid);
} catch (error) {
console.log(error);
}
const expoToken = await UserPermissions.registerForPushNotificationsAsync();
if (expoToken) {
this.props.firebase.setExpoToken(expoToken);
}
if (this._isMounted) {
this.setState({ isLoading: false });
}
};
search = async () => {
Keyboard.dismiss();
await axios(this.apiurl + "&s=" + this.state.searchText).then(
({ data }) => {
let results = data.Search;
if (this._isMounted) {
this.setState((prevState) => {
return { ...prevState, results: results };
});
}
}
);
};
After a successful login, you call the navigate function. This navigates to a different component, which means the login component becomes unmounted. The handleLogin function still has more logic to execute though, in your finally statement, you are setting submission to false. When that finally runs there is no mounted component which means there is no state to set.
Moving your submission false state change to before the navigate call and before the alert will solve the problem.
I’d recommend not bothering with it in the case the user actually logs in, because the user is about to visually move to a completely different screen, changing the state doesn’t really help them.
I have a react application that should sign up a user and add the user's info to a collection using the uid. I am using redux and have broken my code into component, reducer and action. This is the add user component:
state = {
name : '',
email : '',
password : '',
position : '',
department : '',
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.signUp(this.state)
}
render() {
return(//I have my input fields and submit button here)
}
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return {
signUp: (newUser) => dispatch(signUp(newUser))
}
}
export default connect (mapStateToProps, mapDispatchToProps)(AddUser);
I do my auth action in the authAction with:
export const signUp = (newUser) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
const firestore = getFirestore();
firebase.auth().createUserWithEmailAndPassword(
newUser.email,
newUser.password
).then((resp) =>{
return firestore.collection('users').doc(resp.user.uid).set({
name: newUser.name,
position: newUser.position,
department: newUser.department
})
}).then(() => {
dispatch({ type: 'SIGNUP_SUCCESS' })
}).catch(err => {
dispatch({ type: 'SIGNUP_ERROR', err})
})
}
}
my Sign Up reducer is:
case 'SIGNUP_SUCCESS':
console.log('Signup success');
return {
...state,
authError: null
}
case 'SIGNUP_ERROR':
console.log('signup error');
return {
...state,
authError: action.err.message
}
The issue is that this creates a user and logs the user in but doesn't create a document with the user data in the firestore collection. I do not get any errors on the console. it also doesn't log the success message. The user is created and can log in though.
What am I doing wrong? Please help.
Thanks!
Do I understand right that it is an async process? If yes, you should also use Redux-Saga library for this(I mean it would be better practice).
However, if you are not willing to implement that try adding debugger or console log data in sign up action before return to see if you passing the right data in the right format for firebase. Same goes to catch block, console log error, I may say useful information if the error is in request/response.
I don't know how can I easy resolve the problem. I have a form that user can fill and send stuff to my database. Form has his own state for it (here for example is just username). I don't know how to call setState (to give message to user about result) after dispatching redux action.
Function postDataBegin is simple and shows spinner only. I think there is a problem, cause this function is synchronous (tell me if not). How can i fix it? What should i learn?
submitForm = e => {
e.preventDefault();
const { username } = this.state;
if (!question) {
this.setState({ messageToUser: "Fill the form" });
} else {
// onSubmit is redux action from props
this.props.onSubmit(username);
//HOW TO CALL THIS FUNCTION AFTER FUNC ABOVE?
this.setState({ messageToUser: "Send" });
}
};
<Form type="submit" onSubmit={this.submitForm}/>
export const postUserQuestion = username => dispatch => {
dispatch(postDataBegin());
return post(postUsernameAPI, username)
.then(res => dispatch(postUsernameAPISucceeded(res)))
.catch(error => dispatch(postUsernameAPIFailure(error)));
};
Your action returns a promise, so you can use then method on it to provide a callback:
submitForm = e => {
e.preventDefault();
const { username } = this.state;
if (!question) {
this.setState({ messageToUser: "Fill the form" });
} else {
// onSubmit is redux action from props
this.props.onSubmit(username).then(() => {
this.setState({ messageToUser: "Send" });
});
}
};
Altho it'd probably be better to store that data on reducer as well, and change it the same time when the form is submitted.
I was searching for a simple Login/Registration System and stumbled about this tutorial:
http://jasonwatmore.com/post/2017/09/16/react-redux-user-registration-and-login-tutorial-example
I tried it and added a real mongoDB Backend to remove the Mock-Backend. It was my first time doing Redux, but after a lot of struggle everything is working finde now! You can see the result here :
https://genko.de/login
Just one thing is not working at all and i have no clue how to solve it. Since I want to avoid bootstrap, I have a big problem with the error handling.
I want to display a snackbar from Material-Ui for following cases:
Username or Password is invalid
Successfull Logged in
Username is already taken (Registration)
In fact there are already some actions and reducers in my redux files, but to be honest, I just copied them from the tutorial and I have no clue how to work with them.
Following function is triggered by the Login button:
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
const { dispatch } = this.props;
if (username && password) {
dispatch(userActions.login(username, password))
}
}
And this is my login function (user.actions):
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.goBack();
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
alert.actions:
import { alertConstants } from '../_constants';
export const alertActions = {
success,
error,
clear
};
function success(message) {
return { type: alertConstants.SUCCESS, message };
}
function error(message) {
return { type: alert, message };
}
function clear() {
return { type: alertConstants.CLEAR };
}
And finally my alert.constants:
export const alertConstants = {
SUCCESS: 'ALERT_SUCCESS',
ERROR: 'ALERT_ERROR',
CLEAR: 'ALERT_CLEAR'
};
Do you have some hints for me or suggestions?
Best regards :)
EDIT:
I forgot to show you my NodeJS Global Error Handler Middleware from this follow up tutorial to replace the face-backend:
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
if (typeof (err) === 'string') {
// custom application error
return res.status(400).json({ message: err });
}
if (err.name === 'ValidationError') {
// mongoose validation error
return res.status(400).json({ message: err.message });
}
if (err.name === 'UnauthorizedError') {
// jwt authentication error
return res.status(401).json({ message: 'Invalid Token' });
}
// default to 500 server error
return res.status(500).json({ message: err.message });
}
you need to have an entries in your reducer for LOGIN_SUCCESS and LOGIN_FAILURE that will set the state in the redux store to something that you can use back in your component.
function reducer(state = { status: ''}, action) {
switch (action.type) {
case 'LOGIN_SUCCESS':
return { status: 'LOGGED_IN'}
... ETC
default:
return state
}
}
Then via mapStateToProps you will map the state of the redux store to props of the component and be able to use it like -
const mapStateToProps = (state) => ({
status: state.status
})
this.props.status // LOGGED_IN