onCompleted() is called in useMutation because i want to open=true (open is a flag) when mutation is done than this flag value should pass to const App() so that open={open} and alert is shown. but the value of open is false throughout process.
(2). If i set open={true}, than Welcome popUp shown on screen with login button, but when i press login button and onclick() triggers it gives error "cannot read property of 'setNewUser'", where setNewUser is const define in index file.
Register.js
const App = ({ values, errors, touched, isSubmitting, dirty , isValid, setNewUser }) => {
const styles= useStyles();
const [open, setOpen]= useState(false) ; //On new user dialog will open
// console.log(open)
return (
<div className={styles.root}>
<Paper className={styles.paper}>
<Form>
<Field type="username" name="username" placeholder="Username" />
<div>
{touched.email && errors.email && <p>Invalid Email </p>}
<Field type="email" name="email" placeholder="Email" />
</div>
<div>
{touched.password && errors.password && <p>Invalid password </p>}
<Field type="password" name="password" placeholder="Password" />
</div>
<button disabled={isSubmitting || !isValid || !dirty }>
Register
</button>
</Form>
</Paper>
<Dialog
// Here is the flag, its not changing to true
open={open}
disableBackDropClick={true}>
<DialogTitle>
<VerifiedUserTwoTone className={styles.icon}/> New Account created
</DialogTitle>
<DialogContent>
<DialogContentText> Welcome {values.username} </DialogContentText>
</DialogContent>
<DialogActions>
<button onClick={()=>setNewUser(false)}>Login</button>
</DialogActions>
</Dialog>
</div>
);
};
const FormikApp = withFormik({
// enableReinitialize: true,
mapPropsToValues({ email, password, username }) {
return {
username: username || " ",
password: password || " ",
email: email || " ",
};
},
validationSchema: Yup.object().shape({
email: Yup.string()
.email("Invalid email account")
.required("field missing"),
password: Yup.string()
.min(8, "password is weak")
.required()
}),
handleSubmit(values, {setStatus, props, setSubmitting }) {
//event.preventDefault();
props.createUser({
variables: values,
}, );
setTimeout(()=>{
setSubmitting(false)
setStatus("sent");
console.log("Thanks!");
},1000);
}
})(App);
const Register = () => {
const [createUser,{loading,error}] = useMutation(REGISTER_MUTATION ,
{
onCompleted(){
// setOpen(true)
}}
);
if (loading) return <p>loading...</p>;
if (error) return <p>An error occurred</p>;
return <FormikApp createUser={createUser} />;
};
export default Register;
index.js (local index for forms)
import React,{useState} from "react";
import withRoot from "../withRoot";
import Login from "./Login"
import Register from "./Register"
export default withRoot(() => {
const [newUser, setNewUser]= useState(true)
return newUser? //if user is new than goto Register otherwise Login page
<Register setNewUser={setNewUser}/>
) : (
<Login/>
)
});
Login page is replica of Register (excluding mutation). I am stuck on this point, any contribution is welcomed. Thanks
You didn't passed setNewUser down to the <Register/>:
return <FormikApp createUser={createUser} setNewUser={props.setNewUser} />;
You cannot use setOpen in onCompleted as it's defined inside child. You need to define it higher (in <Register />) and pass value (open) and setter (setOpen) as props.
Related
I am using "react-mailchimp-subscribe" to integrate with MailChimp and enable users to signup to a mailchimp mailing list upon completing an submitting my form.
However, I get the error:
Uncaught TypeError: onValidated is not a function
When I trigger the function that should submit the form info to MailChimp.
Here is a sample of my code:
import MailchimpSubscribe from "react-mailchimp-subscribe";
...
const CustomForm = ({
className,
topOuterDivider,
bottomOuterDivider,
topDivider,
bottomDivider,
hasBgColor,
invertColor,
split,
status,
message,
onValidated,
...props
}) => {
...
const [email, setEmail] = useState('');
const [fullName, setFullName] = useState('');
const handleSubmit = (e) => {
console.log('handlesubmit triggered')
e.preventDefault();
email &&
fullName &&
email.indexOf("#") > -1 &&
onValidated({
EMAIL: email,
MERGE1: fullName,
});
}
...
<form onSubmit={(e) => handleSubmit(e)}>
<h3>
{status === "success"
? "You have successfully joined the mailing list"
: "Join our mailing list by completing the form below"
}
</h3>
<div className="cta-action">
<Input
id="email"
type="email"
label="Subscribe"
labelHidden hasIcon="right"
placeholder="Your email address"
onChange={e => setEmail(e.target.value)}
value={email}
isRequired
>
</Input>
<Input
id="name"
type="text"
label="Subscribe"
labelHidden hasIcon="right"
placeholder="Your full name"
onChange={e => setFullName(e.target.value)}
value={fullName}
isRequired
>
</Input>
<Input
type="submit"
formValues={[email, fullName]}
/>
</div>
</form>
This is the parent component passing down props to the above:
import React from "react";
import MailchimpSubscribe from "react-mailchimp-subscribe";
const MailchimpFormContainer = props => {
const postURL = `https://*li$%.list-manage.com/subscribe/post?u=${process.env.REACT_APP_MAILCHIMP_U}$id=${process.env.REACT_APP_MAILCHIMP_ID}`;
return (
<div>
<MailchimpSubscribe
url={postURL}
render={({ subscribe, status, message }) => (
<CustomForm
status={status}
message={message}
onValidated={ formData => subscribe(formData) }
/>
)}
/>
</div>
);
};
I have the following Form:
const MyForm = () => {
return (
<>
<Formik
validateOnChange={true}
initialValues={{ plan: "", email: "", name: "" }}
validate={values => {
console.log(values)
const errors = {}
if (values.plan !== "123" && values.plan !== "456") {
errors.plan = "Not valid"
} else if (values.plan === "") {
errors.plan = "Please enter something"
}
if (!values.email) {
errors.email = "Please provide an e-mail address."
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Please provide a valid e-mail address."
}
return errors
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false)
}, 400)
}}
>
{({ isSubmitting, errors }) => (
<Form>
<FieldWrapper>
<InputField type="text" name="plan" label="plan" />
<StyledErrorMessage name="plan" component="div" />
</FieldWrapper>
<Button
disabled={errors.plan}
>
Continue
</Button>
</Form>
)}
</Formik>
</>
)
}
I have a Continue Button and I want it to be disabled if there are any errors. I am doing <Button disabled={errors.plan}> and this works.
However: it does not disable to Button when the user just doesn't touch the field at all - since then, the validation isn't called and consequently, there won't be any errors in the error object. So initially, the button is not disabled.
How can I circumvent this?
I'm not too familiar with Formik, but could you add a state for completed status of the form, that is initially set to false, and when completed setState(true). Then your conditional for <Button> can check both errors.plan && completedState.
I came across a SO overflow which suggested that we can use e.target.reset()on forms to reset forms after submitting. However, I am unable to use it in my case:
export default function RemoveUserPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [removeUser] = useMutation<DeleteUserReponse>(REMOVE_USER);
let submitForm = (email: string) => {
setIsSubmitted(true);
removeUser({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<DeleteUserReponse>) => {
setIsRemoved(true);
}})
};
const initialSTATE={ email: '' }
return (
<div>
<Formik
//initialValues={{ email: '' }}
initialValues={{ ...initialSTATE}}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div className="main-content">
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
e.target.reset();
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
<br></br>
<CustomButton
disabled={!isValid || !email}
text={'Remove User'}
/>
</div>
</form>
<br></br>
{isSubmitted && StatusMessage(isRemoved, errorMessage)}
</div>
);
}}
</Formik>
</div>
);
}
If I use it at the end of my onSubmit, I get the error mentioned above. How can I fix this?
I had the same problem, since I know that the e.target is a form element I just set it's type like so:
const target = e.target as HTMLFormElement
target.reset()
It works. I hope this helps you.
I came across a SO overflow which suggested that we can use e.target.reset() on forms to reset forms after submitting.
Could be valid before react era. Nowadays react updates real DOM on props changes.
Want to reset form? Render it using new values!
Based on docs you should pass handleSubmit prop to rendered <form/>:
<form onSubmit={props.handleSubmit}>
This handleSubmit will call (after validation) Formik's onSubmit handler. This is a place where you should define custom logic.
onSubmit is called with (values, actions) so you can
submitForm(values.email);
actions.resetForm()
or pass actions to your submitForm and use resetForm in this method.
I am using building a react app Here I am unable to figure out how to redirect to /dashboard after successfull login. Any help would be usefull
In this file i am passing my username and password to my redux action this.props.login
signin.js
handleLoginClick = () => {
const { inputUserID, inputPassword } = this.state;
this.login(inputUserID, inputPassword)
};
login = (username, password) => {
this.props.login(username, password);
};
render() {
const {
showError,
open,
inputUserID,
inputPassword,
checkRememberID,
showPhoneNumberDialog,
showVerifyNumberDialog
} = this.state;
return (
<div className="sign-in-dialog-container">
<Dialog
id="dialog-sign-in"
className="dialog"
open={open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
style={{
backgroundColor: '#fff'
}}
>
<DialogTitle id="form-dialog-title">Sign In</DialogTitle>
<DialogContent className="dialog-content">
<div className="dialog-content-form">
<div className="form-field">
<div className="content-label">ID</div>
<FormControl fullWidth variant="outlined">
<OutlinedInput
fullWidth
type="text"
placeholder="User Name"
value={inputUserID}
onChange={this.handleTextChange("inputUserID")}
labelWidth={0}
/>
</FormControl>
</div>
<div className="form-field margin-top-16">
<div className="content-label">Password</div>
<FormControl fullWidth variant="outlined">
<OutlinedInput
fullWidth
type="password"
placeholder="**********"
value={inputPassword}
onChange={this.handleTextChange("inputPassword")}
labelWidth={0}
/>
{showError ? (
<FormHelperText className="password-incorrect-text">
Password is incorrect
</FormHelperText>
) : null}
</FormControl>
</div>
<div className="form-field">
<FormControlLabel
control={
<Checkbox
checked={checkRememberID}
onChange={this.handleCheckboxChange("checkRememberID")}
value="checkRememberID"
color="primary"
/>
}
label="Remember ID"
/>
</div>
<div className="form-field">
<Button
className="next-button custom-button-style"
fullWidth
onClick={this.handleLoginClick}
variant="contained"
color="primary"
>
Next
</Button>
</div>
</div>
</DialogContent>
<div className="bottom-row">
<span className="helper-text">
Don't have an account?
<span
onClick={this.handleSignUpClick}
className="strong cursor-pointer"
>
{" "}
Sign Up
</span>
</span>
</div>
</Dialog>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.userReducer,
productPageReducer: state.productPageReducer,
isAuthenticated: state.auth.access_token,
});
const mapDispatchToProps = {
getProductList,
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignIn);
authAction.js
export const login = (username, password) => (dispatch) => {
//User Loading:
dispatch({ type: USER_LOADING });
//Make API Call here
var myHeaders = new Headers();
myHeaders.append("Authorization", "Basic dG9wc2VsbGVyOnRvcHNlbGxlcg==");
var formdata = new FormData();
formdata.append("username", username);
formdata.append("password", password);
formdata.append("grant_type", "password");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("https://api.XXXXXXXXXXXXX.com/oauth/token", requestOptions)
.then(response => response.json())
.then(
res => {
if (res.access_token && res.refresh_token) {
window.localStorage.access_token = res.access_token;
window.localStorage.refresh_token = res.refresh_token;
dispatch({
type: LOGIN_SUCCESS,
user: res
});
} else {
dispatch({
type: LOGIN_FAIL,
message: 'Unauthorised User'
})
}
}
)
.catch(error =>
dispatch({
type: LOGIN_FAIL,
message: error
})
);
}
Here i want to figure out how to redirect to /dashboard after successfull login.I am also using react-router-dom.
You can do this but DO NOT:
CASE LOGIN_SUCCESS:
window.location.href = '/dashboard';
break;
This is just a hack. Redirecting with window.location.href makes your app lose SPA advantages.
Since you're using React-redux, you must hold user login state in reducer. And let App.jsx subscribe login state and if it's false render a Redirect component.
Why your app forgets login state after redirect:
Because you're doing something wrong with window.localStorage:
window.localStorage.whatever_token = 'foobarbaz';
This won't work. localStorage was not meant to be used like this.
Instead, you should:
window.localStorage.setItem('my_token', 'awesome_jwt_token');
Later:
const isLoggedIn = () = > {
const token = window.localStorage.getItem('my_token');
if (token) {
// can ommit this flow
// const isLoggedIn = askServerIfThisTokenIsValid(token);
// if (isLoggedIn) {
// return true;
// } else {
// window.localStorage.removeItem('my_token');
// }
return true; // but not safe - handle 403 error in all api calls
} else {
return false;
}
};
It seems like after successful sign-in attempt, { user } state at your redux store change.
therefore you might consider add this at signin.js
import {Redirect } from 'react-router-dom';
class SignIn extends Component {
...rest class..
render ()
const { user } = this.props;
return(
<>
{ if (user)<Redirect to="/dashboard" />;} // in case { user } is null at initial state, otherewise change the checker...
...rest code...
</>
)
}
working example - https://github.com/zero-to-mastery/visual-music/blob/development/src/pages/Login/Login.js
One way to do this is to use the redux state change to decide which component to render. For instance, you can have a property like 'IsLoggedIn' in the redux state and when its 'true' render the '/dashboard' component. something like:
if(IsLoggedIn) return (<Dashboard />)
else return (<Login />)
I am building an app with ReactJS that authenticate users through a Laravel API.
This is a very simple app. And the authentication part is working well. But after authentication when I try to change the state I am getting this error.
Cannot update during an existing state transition
The App I am trying to build is a very simple one. After the app makes an API call to authenticate the user, it must get the user's data from the API response and pass those responses to the component set for logged in users.
I made some search on the error Cannot update during an existing state transition. All the answers I am getting are telling me not to setState inside of a render. I am not doing that.
Here is my code
App.jsx
//...
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false,
user: {}
};
}
_loginUser = (email, password) => {
var formData = new FormData();
formData.append("email", email);
formData.append("password", password);
axios
.post("http://localhost:8000/api/user/login", formData)
.then(response => {
console.log(response);
return response;
})
.then(json => {
if (json.data.success) {
let userData = {
first_name: json.data.data.first_name,
name: json.data.data.name,
id: json.data.data.email,
email: json.data.data.email,
auth_token: json.data.data.auth_token,
timestamp: new Date().toString()
};
let appState = {
isLoggedIn: true,
user: userData
};
//Save app state with user date in local storage
localStorage["appState"] = JSON.stringify(appState);
this.setState({
isLoggedIn: appState.isLoggedIn,
user: appState.user
});
} else {
$("#feedbacks").html(
`<span className="alert alert-danger">Login Failed: ${
json.data.data
}</span>`
);
}
$("#email-login-btn")
.removeAttr("disabled")
.html("Login");
})
.catch(error => {
$("#feedbacks").html(
`<span className="alert alert-danger">An Error Occured</span>`
);
$("#email-login-btn")
.removeAttr("disabled")
.html("Login");
});
};
componentDidMount() {
let state = localStorage["appState"];
if (state) {
let AppState = JSON.parse(state);
console.log(AppState);
this.setState({
isLoggedIn: AppState.isLoggedIn,
user: AppState
});
}
}
render() {
if (
!this.state.isLoggedIn &&
this.props.location.pathname !== "/app/login"
) {
this.props.history.push("/app/login");
}
if (
this.state.isLoggedIn &&
(this.props.location.pathname === "/app/login" ||
this.props.location.pathname === "/app/register")
) {
this.props.history.push("/");
}
return (
<Switch data="data">
<Route
exact
path="/"
render={props => (
<Home
{...props}
logOut={this._logoutUser}
isLoggedIn={this.state.isLoggedIn}
user={this.state.user}
/>
)}
/>
<Route
path="/app/login"
render={props => (
<Login {...props} loginUser={this._loginUser} />
)}
/>
</Switch>
);
}
}
const AppContainer = withRouter(props => <App {...props} />);
render(
<BrowserRouter>
<AppContainer />
</BrowserRouter>,
document.getElementById("root")
);
Login.jsx
const Login = ({ history, loginUser = f => f }) => {
console.log(history);
let _email, _password;
const handleLogin = e => {
e.preventDefault();
loginUser(_email.value, _password.value);
};
return (
<div>
<div id="wrapper" className="formWrapper">
<form method="post" action="#" onSubmit={handleLogin}>
<div className="fields">
<div className="field">
<label htmlFor="email">Your Email</label>
<input
ref={input => (_email = input)}
autoComplete="off"
id="email"
name="email"
type="text"
className="center-block"
placeholder="email"
/>
</div>
<div className="field">
<label htmlFor="password">Your Password</label>
<input
ref={input => (_password = input)}
autoComplete="off"
id="password-input"
name="password"
type="password"
className="center-block"
placeholder="password"
/>
</div>
</div>
<div className="actions login">
<button
type="submit"
className="primary"
id="email-login-btn"
>
<span className="icon fa-user" /> Login
</button>
</div>
</form>
</div>
<div id="blur" />
<div id="bg" />
</div>
);
};
export default Login;
The _login method handles the form submit. And within that method, I setState the user once authentication is valid. Apparently, that is where the error is coming from. That generates the error :
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
So I need to know where is the right place to change the value of the state 'user'.
Can someone help, please?