I have a form in Reactjs where I am expected to collect the information and sent to my API. This information include, Phonenumber, gender, date of birth and country of birth.
I have written my react handleChange code as follows:
handleChange (event) {
const { name, value } = event.target;
const { info } = this.state;
this.setState({
info: {
...info,
[name]: value
}
});
}
Then I have written my return function as follows
return (
<CardBody>
<form name="form" onSubmit={this.handleSubmit}>
<div className="grey-text">
<PhoneInput
placeholder="Enter phone number"
value={ info.phone_number }
onChange={this.handleChange}
error={ info.phone_number ? (isValidPhoneNumber(info.phone_number) ? undefined : 'Invalid phone number') : 'Phone number required' }/>
<Select
value={info.gender}
onChange={this.handleChange}
options={sex}
/>
<DatePicker
selected={info.dbirth}
onChange={this.handleChange}
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
/>
<CountryDropdown
value={info.nationality}
onChange={this.onChangeh} />
</div>
<div style={{backgroundColor:"#006400"}}>
<Button type="submit" onClick={this.handleSubmit} className=" btn-success btn-block" >Validate Account</Button>
</div>
</form>
</CardBody>
);
}
};
When I try compile my project it compiles well but when I try to select any of the dropdowns such as phone number, gender I get the error
TypeError: event is undefined or TypeError: _event$target is undefined
I am quite new to reactjs. I have tried looking for answer to no avail . Any help on this will be appreciated
you're getting the error because the <Select /> will pass the value to the onChange unlike the <Input /> which passes an event , one way to get around this it to pass a similar object :
<Select
value={info.gender}
onChange={value => this.handleChange({target : {name : 'mySelect', value }}) }
options={sex}
/>
( probably the same for the datePicker )
You can simply define any variable in state. I defined selectedOption and used this code. This should help
handleSelect = (selectedOption) => {
this.setState({
selectedOption
})
}
and call in onChange={this.handleSelect}
Related
I am using the Context API to add user details from a form to a global state. When I submit the form, the state is always "one step behind" - essentially, a double click is required to get the desired result.
Basic recreation of the code (have removed irrelevant bits and some imports):
import { UserProvider, UserContext } from "../../StateContext"
export default function SignUp() {
const user = useContext(UserContext)
const history = useHistory()
const handleSubmit = (e) => {
e.preventDefault()
user.setName(userDetails.name)
//logging out the user object here will result in the previous values shown
}
const [userDetails, setUserDetails] = useState({
name: null,
age: null,
})
return (
<>
<form onSubmit={handleSubmit}>
<div className="form-vertical-batch">
<FormControl>
<Input
type="text"
placeholder="Your Name"
required={true}
onChange={(e) =>
setUserDetails({ ...userDetails, name: e.target.value })
}
></Input>
<FormHelperText>
Put the name you're best known by online - either a nickname,
brand name, or your real name.
</FormHelperText>
</FormControl>
<FormControl>
<TextField
type="number"
inputProps={{ min: 18, max: 99 }}
onChange={(e) =>
setUserDetails({ ...userDetails, age: e.target.value })
}
/>
<FormHelperText>
You currently must be at least 18 years old to use the platform.
</FormHelperText>
</FormControl>
</div>
</div>
<input
id="formButton"
className="btn sign-up-button"
type="submit"
placeholder="Send message"
/>
</form>
</>
)
}
To clarify the issue here - if I submit with a name as "Reikon" and log our the user object, the first time it will return as null, and then the second time it will return "Reikon" as expected.
I am working on a scenario where I have to do a multi-step form which I have already done, as well as the validation part. I am using react-hook-form for validation.
I have multi-step form:
in the first form I have several fields and one radio button
by default radio button is ticked on for auto generated pass so in this case I have nothing to do
the second one is let me create a password so in this case one input field will be show and the user will create the password
Issue
In my final form I am doing the validation like below:
{
fields: ["uname", "email", "password"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
So to validate uname, email and password I am passing the values like above.
But when the radio button is ticked for auto generated password it is still handling the validation, I click on next and it is not going to next for because of password field.
And if I check the radio button as let me create the password it goes to next form and when I came back by clicking back it is going to auto generated password again and it is not holding the previous state. For other input fields it is handling the previous values but not in case of radio button scenario.
My full working code sandbox
Answer 1 The reason is you fields: ["uname", "email", "password"] is fixed, password is always to be taken validation.
Solution Need to store state of Form1 in App so you can check if the state of auto generated password is on remove password from the list
App.js
... other code
// need to move state and function form Form to app
const [show_input, setshow_input] = useState(false);
const createInput = () => {
setshow_input(true);
};
const auto_text = () => {
setshow_input(false);
};
const forms = [
{
// validate based on show_input state
fields: show_input ? ["uname", "email", "password"] : ["uname", "email"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
auto_text={auto_text}
createInput={createInput}
show_input={show_input}
/>
)
},
{
fields: ["lname"],
component: (register, errors, defaultValues) => (
<Form2
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
{
component: (register, errors, defaultValues) => (
<Form3
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
}
];
... other code
Answer 2 When you go next the Form1 is unmounted so its state is destroyed. When you store Form1's state in App.js you will fix this issue too
Bonus: It's prefered to use camalCase (E.g: showInput) rather than underscore (show_input)
The main problem is that you render the forms conditionally so all the previous form values are removed. The solution for this is to keep all forms mounted and just use display: none or display: block depending on which form is selected. This way all values will be persisted whenever you go to next or prev form or submit the form.
The second problem that you didn't remove the password field when it's unmounted so when moveToNext is called the valid argument in triggerValidation callback is always false. I fixed that by setting the fields for Form1 conditionally depending on if the password input is visible or not.
The third problem you are using defaultValues for the wrong purpose. You can get the current form values using getValues() which will return all the current values of the form.
I set the default value for uname field just as an example to show you how defaultValues should be used.
you can check the full solution here: https://codesandbox.io/s/fragrant-forest-75pzs?file=/src/App.js
here are all the changed files:
App.js
import React, { useState } from "react";
import Form1 from "./components/Form1";
import Form2 from "./components/Form2";
import Form3 from "./components/Form3";
import { useForm } from "react-hook-form";
function MainComponent() {
const {
register,
triggerValidation,
defaultValues,
errors,
getValues
} = useForm({
// You can set default values here
defaultValues: {
uname: "Lol"
}
});
console.log("Errors: ", errors);
const [currentForm, setCurrentForm] = useState(0);
// control password input visibility and Form1 fields
const [passwordVisible, setPasswordVisible] = useState(false);
const showPassword = () => {
setPasswordVisible(true);
};
const hidePassword = () => {
setPasswordVisible(false);
};
const forms = [
{
fields: passwordVisible
? ["uname", "email", "password"]
: ["uname", "email"],
component: (register, errors) => (
<Form1
// a key is needed to render a list
key={0}
// this will be used to set the css display property to block or none on each form
shouldDisplay={currentForm === 0}
register={register}
errors={errors}
showPassword={showPassword}
hidePassword={hidePassword}
passwordVisible={passwordVisible}
/>
)
},
{
fields: ["lname"],
component: (register, errors) => (
<Form2
key={1}
shouldDisplay={currentForm === 1}
register={register}
errors={errors}
/>
)
},
{
component: (register, errors) => (
<Form3
key={2}
shouldDisplay={currentForm === 2}
register={register}
errors={errors}
values={getValues()}
/>
)
}
];
const moveToPrevious = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm - 1);
});
};
const moveToNext = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm + 1);
});
};
const prevButton = currentForm !== 0;
const nextButton = currentForm !== forms.length - 1;
const handleSubmit = e => {
console.log("whole form data - ", getValues());
};
return (
<div>
<div className="progress">
<div>{currentForm}</div>
</div>
{forms.map(form => form.component(register, errors))}
{prevButton && (
<button
className="btn btn-primary"
type="button"
onClick={moveToPrevious}
>
back
</button>
)}
{nextButton && (
<button className="btn btn-primary" type="button" onClick={moveToNext}>
next
</button>
)}
{currentForm === 2 && (
<button
onClick={handleSubmit}
className="btn btn-primary"
type="submit"
>
Submit
</button>
)}
</div>
);
}
export default MainComponent;
Form1
import React from "react";
function Form1({
register,
errors,
shouldDisplay,
passwordVisible,
showPassword,
hidePassword
}) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User name</label>
<input type="text" name="uname" ref={register({ required: true })} />
{errors.uname && <span>required</span>}
<label>Email</label>
<input type="email" name="email" ref={register({ required: true })} />
{errors.email && <span>required</span>}
</div>
<div>
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<label className="form_label">Password</label>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios1"
value="auto_pass"
className="form-check-input"
defaultChecked={true}
onChange={hidePassword}
/>
Auto generated password
</label>
</div>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios2"
value="let_me"
className="form-check-input"
onChange={showPassword}
/>
Let me create the password
</label>
</div>
</div>
{passwordVisible && (
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mb-3">
<label className="form_label">Password</label>
<input
type="password"
name="password"
className="form-control"
ref={register({ required: true })}
/>
{errors.password && (
<span className="text-danger">Password is reguired</span>
)}
</div>
)}
</div>
</form>
</div>
);
}
export default Form1;
Form2
import React from "react";
function Form2({ register, errors, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User last name</label>
<input type="text" name="lname" ref={register({ required: true })} />
{errors.lname && <span>required</span>}
</div>
</form>
</div>
);
}
export default Form2;
Form3
import React from "react";
function Form3({ values, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<h3>Want to display all values here like below</h3>
{Object.entries(values).map(([key, value]) => (
<p key={key}>
{key}: {value}
</p>
))}
<br />
<p>So that use can check for any Wrong info</p>
</div>
);
}
export default Form3;
I already built the form in React and it shows the input fields in red borders that'll change to regular borders once someone types it in. I used this example from this React form article link So everything is working except I wanted to add the error message under the input field that displays "Please fill in the blank field" that will disappear once someone starts typing in the field. How do I do this?
Here's my code in Form.js:
import React, { Component } from 'react';
import FormField from './FormFieldBox';
function validate(name, isin) {
// true means invalid, so our conditions got reversed
return {
name: name.length === 0,
isin: isin.length === 0
};
}
export default class PopupForm extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
isin: '',
country: '',
errormessage: ''
}
}
updateInput = (e) =>{
this.setState({[e.target.name]: e.target.value})
}
closePopupSubmit = (e) => {
if (!this.canBeSubmitted()) {
e.preventDefault();
}
let security = { //1.gather security data from form submit
name: this.state.name,
isin: this.state.isin,
country: this.state.country
}
this.props.submitPopup(security); //2.closePopup function, add security data
}
canBeSubmitted() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
cancelPopupSubmit = (e) => {
e.preventDefault()
this.props.cancelPopup();
}
render() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return (
<div className='popup'>
<div className='popup-inner'>
<form onSubmit={this.closePopupSubmit}>
<FormField onChange={this.updateInput} className={errors.name ? "input error" : "input"} label="Name" type="text" name="name" value={this.state.name} />
<FormField onChange={this.updateInput} className={errors.isin ? "input error" : "input"} label="ISIN" type="text" name="isin" value={this.state.isin} />
<FormField onChange={this.updateInput} label="Country" type="text" name="country" value={this.state.country} />
<button type="button" onClick={this.cancelPopupSubmit} className="button">Cancel</button>
<button type="submit" className="button" disabled={isDisabled}>Submit</button>
</form>
</div>
</div>
)
}
}
And my component FormField.js
import React from "react";
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
{/* {props.errormessage} */}
</div>
</div>
)
}
export default FormBox;
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
</div>
{Boolean(props.value.length) || (
<div className="err-msg">
Please fill in the blank field
</div>
)}
</div>
)
}
There are two ways you can achieve this
First : oninvalid attribute in HTML5 and calling a custom function on that.
Second : along with each element name object in state have a length attribute. In validation function you can check for the length and throw a custom error that you want to display.
I am unable to type any input into my input field. I am using React, and have already set a handleChange and a handleSubmit function. The first two input fields, for 'name' and 'email', take input just fine. But for 'favoriteCity', it doesn't seem to work.
I am wondering if it is due to a MongoDB error that I am getting.
class UserPage extends Component {
state = {
user: [],
newUser: {
name: '',
email: '',
favoriteCity: ''
}
}
getAllUsers = () => {
axios.get('/api/users')
.then(res => {
this.setState({ user: res.data })
})
}
componentDidMount() {
this.getAllUsers()
}
handleChange = event => {
const newUser = { ...this.state.newUser };
newUser[event.target.name] = event.target.value;
this.setState({ newUser: newUser});
}
handleSubmit = event => {
event.preventDefault()
axios.post('/api/users', this.state.newUser)
.then(res => {
this.props.history.push(`/users/${res.data._id}`)
})
}
render() {
return (
<div>
{ /* This shows a list of All Users */ }
{this.state.user.map(user => (
<div key={user._id}>
<Link to={`/users/${user._id}`}>{user.name}</Link>
</div>
))}
<h1>New User Page</h1>
<form onSubmit={this.handleSubmit}>
<label>Name: </label>
<input
type="text"
name="name"
placeholder="Name?"
value={this.state.newUser.name}
onChange={this.handleChange}
/>
<label>Email: </label>
<input
type="text"
name="email"
placeholder="Email?"
value={this.state.newUser.email}
onChange={this.handleChange}
/>
<label>Favorite City: </label>
<input
type="text"
name="city"
placeholder="Favorite City?"
value={this.state.newUser.favoriteCity}
onChange={this.handleChange}
/>
<Button
type="submit"
value="Submit"
variant="contained"
color="primary"
>
Create User
</Button>
</form>
</div>
);
}
}
export default UserPage;
Please help.
Weird that email works fine, from what you posted your handleChange function is only updating the name on the newUser.
What you should see is what you type in all the inputs appear in the name input.
To fix this, you should probably have separate change handlers for each input:
handleNameChange
handleEmailChange
...
You should also consider storing name, email etc.. at the root of your state instead of nesting them in an object, that'll simplify the handler functions code.
I want to use react-widgets calendar as input for start date and end date,
simplest implementation works:
const TripValidationForm = React.createClass({
FormDatepicker : ({input}) => {
return (<Calendar {...input} /> );
},
onSubmit : function(formData) {
const uid = this.props.auth.uid;
this.props.createTrip(formData, uid);
},
render : function() {
const {handleSubmit, submitting, feedback} = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit)} className="form-home">
<fieldset>
<Field name="startdate" valueField="value" component={this.FormDatepicker} />
<Field name="enddate" valueField="value" component={this.FormDatepicker}/>
</fieldset>
<p className="error">{feedback.msg}</p>
<button type="submit" className="btn btn-black" disabled={submitting}>Submit</button>
</form>
</div>
);
}
});
However following errors pop in my console, how I can prevent them from showing up?
looks like the warnings are coming from the props validation on the react widget calendar source, not much you can do unless you want to modify the source. Also this warnings are only shown in dev mode, if you bundle your code for production you shouldn't see any of this warnings
Found a solution, docs for a calendar widget say
The current selected date, should be a Date object or null.
So I used solution from one redux-form git-hub issues for datepicker
FormDatepicker : ({input}) => {
const selected = input.value ? new Date(input.value) : null;
return (<Calendar {...input} value={selected} /> );
}