If the mutation is successful, I am trying to setAdded to true in the .then of ```submitForm()``. If this is true, I want to show a message from the SuccessfulMessage(). However, when I log the value of added, I keep seeing false.
Since addedis not changed to true. I am unable to see any message when mutation is successful. Why doesn't it change?
export default function AddUserPage() {
const [state, setState] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
phoneNumber:'',
loggedIn: false,
});
const [added, setAdded] = useState(false);
function SuccessMessage(){
if (added)
{
console.log('User Added');
return (
<Typography>
User Added
</Typography>)
}
}
useEffect(() => {
if(added){
SuccessMessage();
}
},[] );
function submitForm(AddUserMutation: any) {
const { firstName, lastName, email, password, phoneNumber } = state;
if (firstName && lastName && email && password && phoneNumber) {
AddUserMutation({
variables: {
firstName: firstName,
lastName: lastName,
email: email,
password: password,
phoneNumber: phoneNumber,
},
}).then(({ data }: any) => {
setAdded(true);
console.log('doing', added);
console.log('ID: ', data.createUser.id);
console.log('doing', added);
})
.catch(console.log)
}
}
return (
<Mutation mutation={AddUserMutation}>
{(AddUserMutation: any) => (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
initialValues={{ firstName: '', lastName: '', email: '', password: '', phoneNumber: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { firstName, lastName, email, password, phoneNumber },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<div className='main-content'>
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(AddUserMutation);SuccessMessage()}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="firstName"
name="firstName"
helperText={touched.firstName ? errors.firstName : ""}
error={touched.firstName && Boolean(errors.firstName)}
label="First Name"
value={firstName}
onChange={change.bind(null, "firstName")}
/>
<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")}
/>
<Button
type="submit"
disabled={!isValid || !email || !password}
>
Add User</Button>
</div>
</form>
</div>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
Your console.log() directly after calling setAdded will not show true as state updates are async an will only be visible on the next render. Also your SuccessMessage will never be triggered because you did not provide any dependencies for your useEffect(). This means it will only ever be called after mount
You need to add added to the dependency list:
useEffect(() => {
if(added){
SuccessMessage();
}
},[added]);
But actually I don't see any reason to trigger it in a useEffect anyways. Why not just call it in the mutation handler?
Also if you are already using hooks you can use useMutation.
Also you can't return JSX from a handler. It will not do anything. How should react even know where to display your <Typography>User Added</Typography>? You must render everything in the component itself depending on the state.
Sorry, but ...
... it looks like a great example of abusing react ... result of skipping basic tutorials, docs and mixing random oudated examples.
async nature of useState (and setState) - you can't expect updated value;
using useEffect param without knowledge how it affects behaviour;
'useEffect' not required at all;
returning components from event handler instead of [data driven] conditional rendering;
if useMutation used you don't need to pass mutation as param to event handler;
you're using Formik (with fields validation) then checking params (fields) in handler is simply unnecessary;
you can simply use variables: { ...state } if variables props names are matching;
Formik manages values, you don't need to duplicate this using local state - any reason?;
Formik has hooks, too ;) use useFormik();
... event handlers, binding...
Related
I am trying to build a registration form, I have built the login form and it works perfectly, but the registration form is quite off.
First I used the react-dev tools to inspect what is going on and I realized that each input value coming from the registration form happens to be in an array.
I went back to the login form to inspect and saw that the value of the input fields is in the right format (strings). But the value from each specific input field in the registration form is in an array.
What could I be doing wrong?
I tried to replicate what I did in the login form in the registration form, but it is still coming in an array. Also, should hooks be kept in its own separate file?
This is what I see from the react-dev tools for the registration form.
Here is a code snippet of what I did.
const Signup = (props) => {
const authContext = useContext(AuthContext);
const { register, clearErrors, isAuthenticated, error } = authContext;
const [loadBtn, updateLoadBtn] = useState(false);
const [user, setUser] = useState({
firstName: "",
lastName: "",
email: "",
password: "",
username: "",
phoneNumber: "",
});
useEffect(() => {
if (isAuthenticated) {
successMessage();
props.history.push("/dashboard");
}
if (error) {
missingValue(error);
updateLoadBtn(false);
clearErrors();
}
//eslint-disable-next-line
}, [isAuthenticated, error, props.history]);
const { firstName, lastName, email, password, username, phoneNumber } = user;
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
const onSubmit = (e) => {
e.preventDefault();
updateLoadBtn(true);
if (
!firstName ||
!lastName ||
!email ||
!password ||
!username ||
!phoneNumber
) {
missingValue("Please enter all fields");
updateLoadBtn(false);
clearErrors();
} else {
register({
firstName,
lastName,
email,
password,
username,
phoneNumber,
});
}
};
return (
<Fragment>
<ToastContainer />
<RegContainer className="container-fluid py-4">
<RegInfo />
<RegColumn
onChange={onChange}
onSubmit={onSubmit}
firstName={firstName}
lastName={lastName}
email={email}
password={password}
phoneNumber={phoneNumber}
loadBtn={loadBtn}
/>
</RegContainer>
</Fragment>
);
}
That is the file responsible for handling the registration.
Here is the custom component
const RegColumn = ({
firstName,
onSubmit,
onChange,
lastName,
username,
password,
phoneNumber,
email,
loadBtn,
}) => {
const bodyStyle = document.querySelector("body").style;
bodyStyle.backgroundImage = "linear-gradient(to bottom, #F6F6F2, #C2EDCE)";
bodyStyle.backgroundRepeat = "no-repeat";
bodyStyle.overflow = "hidden";
bodyStyle.height = "100%";
bodyStyle.fontFamily = "Rubik, sans-serif";
return (
<Fragment>
<div id="reg-column">
<h3 style={{ color: "#388087" }}>REGISTRATION</h3>
<Form divid="form-row1-label" onSubmit={onSubmit}>
<LabelContainer id="form-row1-label">
<LabelContainer id="firstNameLabel">
<LabelInfo labelfor="firstName" labeltitle="First Name" />
</LabelContainer>
<LabelContainer id="lastNameLabel">
<LabelInfo labelfor="lastName" labeltitle="Last Name" />
</LabelContainer>
</LabelContainer>
<InputContainer id="form-row1-input">
<InputContainer id="firstNameInput">
<Input
type="text"
name="firstName"
value={firstName}
id="firstName"
onChange={onChange}
/>
</InputContainer>
<InputContainer id="lastNameInput">
<Input
type="text"
onChange={onChange}
name="lastName"
value={lastName}
id="lastName"
/>
</InputContainer>
// ...
Thank you.
Within your Signup form, you have this...
// ...
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
What's happening above?
The onChange() is responsible for updating the values to be submitted.
Within setUser, you are passing a value as a literal array using [e.target.value]
Solution:
Remove the literal array [] and pass value as it's received i.e. e.target.value
The left hand side, e.target.name, is fine since you are actually using a computed property name.
You can also read more about handling forms in react.
For example form with only email field:
const RegistrationForm = (props) => {
const { values, touched, errors, handleChange, handleBlur, handleSubmit, status } = props;
const emailFieldHelp = () => {
const { touched, errors, status, setStatus } = props;
console.log('status: ', status);
if (touched.email && errors.email) {
return errors.email;
}
if (status && !status.isUserAdded) {
setStatus({"isUserAdded": false});
return "User already exist";
}
return null;
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
help={emailFieldHelp()}
validateStatus={setFieldValidateStatus('email')}
label="E-mail"
name="email"
hasFeedback={touched.email && values.email !== ''}
>
<Input
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
};
const RegFormView = withFormik({
validationSchema,
mapPropsToValues: () => ({
email: ''
}),
handleSubmit: async (values, { setErrors, setSubmitting, setStatus }) => {
await userService.createUser('/signup/', values)
.then(response => {
setStatus('');
const status = (response.isAdded)
? {isUserAdded: true}
: {isUserAdded: false};
setStatus(status);
setSubmitting(false);
}, (error) => {
setErrors(error);
});
},
})(RegistrationForm);
When I send form and validate it on sever side, I return 'isAdded' = false or true. Than I set status to {isUserAdded: true} or false if user not added. and I absolutely have no idea how to show this message in form under email field and keep form working. Now I can show message but than I cant send form second time becouse of status already set to {isUserAdded: true}. Do I need somehow to change status when user change email field? but I can't do it becouse of the formik.
here onChange={handleChange} I can pass only handleChange and can't run my function or I can run my function like this onChange={myFunc()} but than its seems impossible to pass handleChange to Formik. (I dont need call it like this handleChange(e) its not work! i need somehow pass it to Formik) I'm total stuck. If you have knowledge in react plz help.
And if you know some examples how to show server side validation messages in react form, links to this examples might be helpful. ty.
The problem arised when I was following this React tutorial.
The goal is to create a simple social media web app with Firebase, React, MaterialUI, etc.
I am around the 5:40:00 mark and I've managed to debug every problem I've encountered up until this point, but right now I've been stuck for more than an hour on something that seems like a stupid mistake. The segment that I'm on is creating the login page. I've successfully created the login form and it functions (meaning that it will redirect the user to the home page if the right user credentials are submitted). What doesn't function thought is the helperText of the input fields. The way it's supposed to work is to display an error and give Helper text with the value of an error object, which is generated by this basic LoginValidaton function in the back-end:
exports.validateLoginData = (userData) => {
let errors = {};
if (isEmpty(userData.email)) {
errors.email = "Must not be empty";
}
if (isEmpty(userData.password)) {
errors.password = "Must not be empty";
}
return {
errors,
valid: Object.entries(errors).length === 0 ? true : false,
};
};
Which yields the following response when called through Postman: linkForPicture
And should look like this: linkForPicture
However, in reality, it gives a bad request error in the console and logs the errors object. linkForPicture
Here is the code for my Login Page without the imports and styling parts:
class login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
loading: false,
errors: {},
};
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
};
handleSubmit = (event) => {
event.preventDefault();
this.setState({
loading: true,
});
const userData = {
email: this.state.email,
password: this.state.password,
};
axios
.post("/login", userData)
.then((res) => {
console.log(res.data);
this.setState({ loading: false });
this.props.history.push("/");
})
.catch((err) => {
this.setState({
errors: err.response.data,
loading: false,
});
});
};
render() {
const { classes } = this.props;
const { errors, loading } = this.state;
console.log(errors);
return (
<Grid container className={classes.form}>
<Grid item sm />
<Grid item sm>
<img className={classes.logo} src={AppLogo} alt="no-nudes"></img>
<Typography variant="h4" className={classes.pageTitle}>
Login
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="email"
name="email"
type="email"
label="E-mail"
className={classes.textField}
helperText={errors.email}
error={errors.email ? true : false}
value={this.state.email}
onChange={this.handleChange}
fullWidth
/>
<TextField
id="password"
name="password"
type="password"
label="Password"
className={classes.textField}
helperText={errors.password}
error={errors.password ? true : false}
value={this.state.password}
onChange={this.handleChange}
fullWidth
/>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Sign-in
</Button>
</form>
<Typography>{errors.email}</Typography>
</Grid>
<Grid item sm />
</Grid>
);
}
}
Although I am not sure, I believe the current code creates an object 'errors' in the state with the following properties when the form is submitted with empty text fields
errors: {
"errors": {
"email": "Must not be empty",
"password": "Must not be empty"
}
}
and so I tried to access it in the helperText with something like {errors.errors.email}, but this results in the following error: linkForPicture
I would be extremely grateful if I received some sort of guidance to what I'm doing wrong.
I think the problem is both with your .catch() { ...setState() and what your API is returning.
You're setting a new object that looks like this:
{
errors: err.response.data,
loading: false
}
but what's inside your err.response.data ie what's being returned by your API?
{
errors: {
email: "Must not be empty",
password": "Must not be empty",
}
}
ANOTHER object that already has errors as the property name.
That's why your state ends up with:
{
errors: {
errors: {
email: "Must not be empty",
password": "Must not be empty",
}
},
loading: false
}
you can either get your API to return just the error object:
{
email: "Must not be empty",
password: "Must not be empty",
}
or in your setState(), set the content of the new piece of state to the errors property your API is returning:
.catch((err) => {
this.setState({
errors: err.response.data.errors, //here
loading: false,
});
Update: Why is helperText={errors.errors.email} giving undefined?
Have a look at how you initialised your state object in your constructor function. Your errors property is an empty object {}.
So in the initial render, before you've clicked submit, your helperText prop in your <TextField> is trying to access an undefined property in your initially declared error object. There's nothing there atm! You only get something there after you click the Submit button.
If you want it to not show undefined, you could always fill it with an empty string:
this.state = {
email: "",
password: "",
loading: false,
errors: {
email: "",
password: ""
},
};
Having said that, this is probably not a very good way to manage error messages. Look into form validation for React. There's alotta libraries out there!
I am trying to utilize dynamic fields with react-hook-form in react native. I initially tried to manually register everything, but that doesn't work. Then I tried to use the useFieldArray hook, but all the newly created fields don't register correctly. Here is my latest approach:
I have a custom component to mimic the web interface for a react native TextInput and forward the ref.
const Input = forwardRef((props, ref) => {
const {
onAdd,
...inputProps
} = props
return (
<View>
<TextInput
ref={ref}
{...inputProps} />
<Button onPress=(() => onAdd()} />
</View>
)
}
I then use this component according to how the useFieldArray docs show in my form except that I have a custom change handler. I also set the ref explicitly and attempt to register the individual new field.
const Inputs = useRef([])
const { control, register, setValue, handleSubmit, errors } = useForm({
defaultValues: {
test: defaultVals // this is an array of objects {title: '', id: ''}
}
})
{
fields.map((field, idx, arr) => (
<Input
key={field.id}
name={`test[${idx}]`}
defaultValue={field.name}
onChangeText={text => handleInput(text, idx)}
onAdd={() => append({title: '', id: ''})
ref={
e => register({ name: `test[${idx}]`
Inputs.curent[idx] = e
})
}
When I click the button for the input it renders a new input as would be expected. But when I submit the data, I only get the defaultVals values and not the data from the new input, though I do have an object that represents that input in the test array. It seems something is off with the registering of the inputs, but I can't put my finger on it.
How do I properly set up useFieldArray or utilize other ways to have dynamic fields in react native with react-hook-form?
you can try handle this problem like me, use each element with 1 input and field like this
<Controller
control={control}
name="username"
rules={{
required: {value: true, message: '* Please enter your username *'},
minLength: {value: 5, message: '* Account should be 5-20 characters in length! *'},
maxLength: {value: 20, message: '* Account should be 5-20 characters in length! *'},
pattern: {value: new RegExp(/^[a-zA-Z0-9_-]*$/), message: '* Invalid character *'},
}}
render={({onChange, value}) => (
<InputRegular
placeholder="username"
icon={{
default: images.iconUser,
focus: images.iconUser2,
}}
onChangeText={(val: string) => {
onChange(val);
setValue('username', val);
}}
value={value}
/>
)}
/>
handleSubmit with
const {register, errors, setValue, getValues, control, handleSubmit} = useForm<FormData>({
defaultValues: {
username: null,
email: null,
password: null,
rePassword: null,
},
mode: 'all',
});
useEffect(() => {
register('username');
register('email');
register('password');
register('rePassword');
}, [register]);
const onSubmit = async (data: any) => {
console.log('====================================');
console.log('data', data);
console.log('====================================');
if (getValues('username')) {
dispatch(
actionGetRegister({
data: {
username: getValues('username'),
email: getValues('email'),
password: getValues('password'),
secondaryPassword: getValues('secondaryPassword'),
},
}),
);
}
};
>
you can see my post for detail i use react-hook-form for react native
https://medium.com/#hoanghuychh/how-to-use-react-hook-form-with-react-native-integrate-validate-and-more-via-typescript-signup-244b7cce895b
first of all thank you so much for your time and good willing to help!
It's the first time I am trying to manage the logic of a form using the formik npm library. It's been really easy to setup and nothing is broken.
But I'm having a problem: The form is being submitted anyway and its causing me troubles because it's redirecting to the /profile and it shouldn't.
This is my form:
<Formik
initialValues={{ identifier: "sdf", password: "fgh" }}
validate={values => {
let errors = {};
// REGEX
let regex = !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
if (!values.identifier) {
errors.identifier = "El correo electrónico es requerido";
} else if (regex.test(values.identifier)) {
errors.identifier = "Invalid email address";
}
if (!values.password) {
errors.password = "El email es requerido";
}
return errors;
}}
handleSubmit={(values, { setSubmitting }) => {
// trying to see what im receiving.
// I've seen this code searching around and I wanted to try
console.log(values, setSubmitting);
setTimeout(() => {
// submit them do the server. do whatever you like!
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
}}
onSubmit={values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
}}
render={({
values,
errors,
handleSubmit,
handleChange,
handleBlur
}) => (
<form onSubmit={handleSubmit} name="login">
<Label htmlFor="login__username">
{i18n.FORM.LABEL.USERNAME}
</Label>
{errors.identifier && (
<span color="red">{errors.identifier}</span>
)}
<Input
value={values.identifier}
type="text"
name="login__username"
placeholder={i18n.FORM.LABEL.USERNAME__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="identifier"
/>
<Label htmlFor="login__password">
{i18n.FORM.LABEL.PASSWORD}
</Label>
{errors.password && (
<span color="red">{errors.password}</span>
)}
<Input
value={values.password}
type="password"
name="login__password"
placeholder={i18n.FORM.LABEL.PASSWORD__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="password"
/>
<ActionsWrapper theme={theme}>
<Button type="submit" size="large" fullWidth>
{i18n.PAGE.LOGIN.BUTTON__SUBMIT}
</Button>
</ActionsWrapper>
</form>
)}
/>
And the function that handles the submit of the form is the one below:
handleOnSubmit = values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
};
I've tried to search how people deals with this but, all the examples I've found did not use anything or care about the prevent of the form.
Why is that? What I'm doing wrong? Any advice? Resources?
Thank you so much for everything!
that's not the default behavior of Forms integrated with formik.
Firstly, I think you're missing the "noValidate=true" attribute on your html form element.
Secondly, I don't think you need "handleSubmit" prop on the Formik tag. Just the onSubmit prop works.