I am using the React-Bootstrap forms. I have around 15 fields that need to be filled out in the form. Does this mean I need to have 15 validation functions (e.g validateName, validateDate etc.)?
How is this generally approached?
My data looks something like this:
state = {
person : {
name: '',
startDate: null,
...
...
active: null
}
}
Say for eg you have 2 input fields
state = {
person : {
name: '',
age: 0
},
nameError: null,
ageError: null
}
handleInput = e => {
const { person } = this.state;
person[e.target.name] = e.target.value;
this.setState({
person
});
}
handleSubmit = () => {
const { person } = this.state;
if(person.name === null){
this.setState({
nameError: 'Name is required',
ageError: null
});
}else if(person.age === 0){
this.setState({
ageError: 'Age is required',
nameError: null
});
}else{
//send the values to the backend
//also reset both nameError and ageError here
}
}
render(){
const { person, nameError, ageError } = this.state;
return(
<div>
<input type='text' name='name' value={person.name} onChange={e => this.handleInput(e)} />
{nameError}
<input type='number' name='age' value={person.age} onChange={e => this.handleInput(e)} />
{ageError}
<button value='Submit' onClick={this.handleSubmit} />
</div>
);
}
Please Let me know if you have further queries. Sorry if there are any typos I answered on my mobile
Related
I am working on a calculating application using ASP.NET Core 6 and React 18
in the .NET application, I am using some logic to return some data, the data is not stored in the database, only temporary/mock data and I am only using POST.
the code in the backend works, it returns what I want.
However, in my frontend application with React. I am using Formik for formhandeling, and it works when entering the data my backend wants in the post endpoint.
When adding a breakpoint to the endpoint it works just as I want.
But I also want the returned data to be visible in my React application.
Let me show you some code.
Backend
[HttpPost("Calculate")]
public ActionResult<TaxModel> PostCalculation(TaxModel model)
{
DateTime date;
if (model == null || !DateTime.TryParseExact(model.Date + " " + model.Time + ":00", "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
return BadRequest(BadRequestMsg);
}
try
{
var vehicle = model.Vehicle;
var calculatePrice = Calculator.GetTollFee(date, vehicle);
return Ok(new TaxModel
{
Date = model.Date,
Time = model.Time,
Vehicle = model.Vehicle,
Price = calculatePrice
});
}
catch
{
return BadRequest(BadRequestMsg);
}
}
Frontend
export const TaxForm = (props: taxFormProps) => {
return(
<Formik initialValues={props.model}
onSubmit={props.onSubmit}
validationSchema={yup.object({
date: yup.string().required("Date is required"),
time: yup.string().required("Time is required"),
vehicle: yup.string().required("Vehicle is required"),
})}>
{(FormikProps) => (
<Form>
<InputField field="date" displayName="Date" />
<InputField field="time" displayName="Time" />
<InputField field="vehicle" displayName="Vehicle" />
<Button className="" type="submit"
text="Calculate" disabled={FormikProps.isSubmitting} />
</Form>
)}
</Formik>
)
}
interface taxFormProps {
model: taxModel;
onSubmit: (values: taxModel, actions: FormikHelpers<taxModel>) => void;
}
export const PostTax = () => {
const [errors, setErrors] = useState<string[]>([]);
const [tax, setTax] = useState<taxModel>();
const create = async (tax: taxModel) => {
try{
await axios.post(`${UrlTax}/Calculate`, tax)
.then(response => {
if(response.status === 200){
setTax(response.data);
}
});
}
catch(error) {
if(error && error.response){
setErrors(error.response.data);
}
}
}
return(
<>
<h3>Calculate your toll Price</h3>
<DisplayErrors errors={errors} />
<TaxForm model={
{
date: "",
time: "",
vehicle: "",
price: 0
}}
onSubmit={async value => {
await create(value);
}} />
{/* {tax?.map(t =>
<p>{t.date} {t.time} {t.vehicle} {t.price}</p>
)} */}
<p>{tax}</p> // does not work
</>
)
}
in the React code, I am trying to add the posted data into the state, but it is not really working.
Any idea on how to fix this error?
Thanks!
I'm trying to make a login component and I think my issue is with React not re-rendering the DOM in my browser but I'm not sure why
If I leave the password field blank when I press the main 'Login' button in my form it will render the alert / warning message .. I can then click this message to dismiss it which is exactly what I want
If I were to repeat the process I would expect the message to be re-rendered and the DOM element reintroduced, however this is not the case - I can see that the loop is being run, I am getting all of the console logs with the correct values, however the loop does not seem to run the 'return' part of my if statement on the second try (in the code below I've added 'this return doesn't re-render' to the console log before that return) - here's my code
Apologies for the large code snippet but I felt it was all relevant for this question
class LoginForm extends Component {
constructor() {
super();
this.state = {
email: "",
password: "",
errors: [],
};
this.onLoginClick = this.onLoginClick.bind(this);
}
onLoginClick() {
const username = this.state.email.trim();
const password = this.state.password.trim();
let errors = [];
console.log("Login press")
if (!EMAIL_REGEX.test(username)) {
errors.push(error_user);
console.log("Username error")
}
if (password === "") {
errors.push(error_pass);
console.log("Password is blank")
}
if (errors.length === 0) {
this.props.onLoginClick(username, password);
if (this.props.loginStatus === login_f) {
errors.push(error_cred);
}
}
this.setState({
errors: errors,
});
console.log("Here are the errors", errors)
}
handleEmailChange = (e) => {
this.setState({ email: e.target.value });
};
handlePasswordChange = (e) => {
this.setState({ password: e.target.value });
};
clearAlertsHandler() {
console.log("Clear alerts")
document.getElementById("misMatch").remove()
}
render() {
let updatedErrors = [...this.state.errors];
return (
<fieldset>
{updatedErrors.map((errorMessage, index) => {
if (errorMessage === error_cred) {
console.log("error_cred match", error_cred, errorMessage)
return (
<button key={index} id={"match"}>{errorMessage} - click to clear</button>
);
} else {
console.log("error_cred mismatch - this return doesn't re-render", error_cred, errorMessage)
return (
<button key={index} id={"misMatch"} onClick={(e) => this.clearAlertsHandler(e)}>{errorMessage} - click to clear</button>
);
}
})}
<label className="text-uppercase">Username</label>
<input
name="email"
type="text"
value={this.state.email}
placeholder="username"
onChange={this.handleEmailChange}
/>
<label className="text-uppercase">Password</label>
<input
className="mb20"
name="password"
type="password"
value={this.state.password}
placeholder="••••••••••"
onChange={this.handlePasswordChange}
/>
<button name="submit" className="primary mb20" onClick={this.onLoginClick}>
Login
</button>
</fieldset>
);
}
In my opinion, React doesn't know that error array changed if you don't clear it.
I think you should do something like this:
clearAlertsHandler() {
console.log("Clear alerts")
this.setState({
errors: [],
});
document.getElementById("misMatch").remove()
}
here I'm trying to load options in select drop down only when minimum of 5 characters are entered. This is because my options list is humungous and it makes my applications performance poor.
I have tried below code, please suggest how can I tweak it to make it work.
this.state= {
options_show: false, // It's false in state
};
inputEntered(e) {
let value = e.target.value;
if (value.length > 5){
this.setState({options_show: true})
}
}
handleChangeName = (e, i) => {
let { nameID, nameIDArray } = this.state;
let filteredID = namesData.filter(name => name.name == e.value)
.map((name) => {
return name.id
})
nameIDArray[i] = filteredID[0];
this.setState({ nameIDArray });
};
<Select placeholder="Enter Profile Name"
name="SID0"
onChange={(e) => this.handleChangeName(e, 0)}
options={this.state.options_show ? options: []}
onInputChange={this.inputEntered}
// openMenuOnFocus={false}
// openMenuOnClick={false}
/>
This does not restrict the input here, on entering each character it starts loading the entire list.
Try with this hope it will work :)
this.state= {
options_show: false, // It's false in state
}
inputEntered(inputText) {
if (inputText.length >= 5) {
this.setState({ options_show: true })
} else {
this.setState({ options_show: false })
}
}
<Select placeholder="Enter Profile Name"
name="SID0"
onChange={(e) => this.handleChangeName(e, 0)}
options={this.state.options_show ? options: []}
onInputChange={(e) => this.inputEntered(e)}
/>
I am maintaining an array of objects which is stored in a state object. Basically I am pushing each object to this array whenever I click on Add button .This stores this object in array.
I am maintaining a flag updateButtonFlag to show the update button for that particular account.
I want to update this flag of an account that just got submitted(that is in onAddAccount() function).
After addition , a new card gets displayed with input fields, so that next user details can be entered
Help would be appreciated
//Have included only onAddAccount function ,where the logic needs to go.
//There is a fetch call as well, which basically displays accounts info if there are any accounts w.r.t to that user
import * as React from 'react';
interface IState{
users : Account[];
user: Account
}
interface Account{
name: string;
email: string;
phone: string;
updateButtonFlag: boolean
}
export default class App extends React.Component<{},IState> {
constructor(props:any){
super(props);
this.state= {
users: [],
user: null
}
}
async componentDidMount(){
let useraccounts = await this.fetchAccounts(); // call that returns accounts, if present
let id:any, account: IAccount ;
if(useraccounts.length === 0) // if no account, display an empty card
{
this.setState({ accounts: [...this.state.accounts, {firstname:'',lastname:'',phone:'',updateButtonFlag: false}]},()=>{});
}
if(useraccounts.length > 0) // if there are accounts existing, display themand add update button to them
{
let accountArray = [];
for(let i=0;i<useraccounts.length;i++)
{
account = {
firstsname: useraccounts[i].firstsname,
lastname: useraccounts[i].lastname,
phone: useraccounts[i].phone,
updateButtonFlag: true
}
accountArray.push(account);
}
this.setState(({accounts}) => ({accounts: [...accounts, ...accountArray]}),()=>{});
}
}
onAddAccount = (index:number) => { // this adds one more card with input fields after submission of current user info
let { users } = this.state;
let account : IAccount = {firstname: users[index].firstname, lastname: users[index].lastname , phone: users[index].phone, updateButtonFlag:false} // maintaining a updateflag to show update button for the corresponding account
this.submit(account); // submit call to submit the account details
//here i need to update the flag of currently submitted account to true, so that update button gets shown , how to do it?
this.setState((prevState) => ({
users: [ ...prevState.users, {firstname:'',lastname:'',phone:''updateButtonFlag:false} ],
}));
} // in this line,next card gets added here
}
renderAccounts = (users: Account[]) => {
return accounts.map((value, index) => {
return (
<div key={index}>
<div>
<form>
<label>First Name:</label>
<input
type="text"
name="firstname"
value={value.firstname}
onChange={e => this.handleChange(e, index)}
required
/>
<label>Last Name:</label>
<input
type="text"
name="lastname"
value={value.lastname}
onChange={e => this.handleChange(e, index)}
/>
<label>Age:</label>
<input
type="text"
name="age"
value={value.age}
onChange={e => this.handleChange(e, index)}
required
/>
<div>
<button onClick={() => this.onAddAccount(index)}>
Save & Add Another Account
</button>
{users[index].updatedButtonFlag?<button onClick={() => this.onUpdateAccount(index)}>
Update Account
</button> :null}
<button onClick={() => this.onRemoveAccount(index)}>
Remove Account
</button>
)}
</div>
</form>
</div>
</div>
);
});
};
render() {
return <div>{this.renderAccounts(accounts)}</div>;
}
}
}
Following what I saw on this thread, you cannot use setState to update nested objects. So, in your case, you'll have to update the entire array.
onAddAccount = (index:number) => {
let { users } = this.state;
let account : IAccount = {firstname: users[index].firstname, lastname: users[index].lastname , phone: users[index].phone, updateButtonFlag:false}
this.submit(account);
users[index].updateButtonFlag = true;
users.push({firstname:'',lastname:'',phone:'',updateButtonFlag:false}); // Add an empty account
this.setState({
users: users,
}));
}
I'm trying to display an error in a form field by adding a className.
This is the render function:
render() {
return (
<div className="row row--no-margin">
<button onClick={this.validate}>Test validation</button>
{
this.props.model.map( (field, index) => {
return this.renderTextField(field);
});
}
</div>
);
}
This is the renderTextField function:
renderTextField(field, index) {
let inputClassNames = 'form-control';
if (this.state.errors.indexOf(field.name) !== -1) {
inputClassNames += ' error-required';
}
return (
<div className={field.wrapperClassName} key={field.key}>
<label className="field-label">{field.label}</label>
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state[field.name]}
/>
</div>
);
}
When i click the button to test validation, the class "error-required" is added to the input, but as soon as i type anything, it loses the class.
This is the onChange function:
handleChange(event) {
this.setState({
[event.target.name] : event.target.value
});
}
The field gets its data from an object:
{
key : 'name',
name : 'name',
type : 'text',
label : 'Full Name',
wrapperClassName: 'col-md-6',
},
Am i missing something?
EDIT:
validate function:
validate() {
let errors = [];
this.props.model.map((m, index) => {
if(!this.state[m.name]){
errors.push(m.name);
}
});
this.setState({
errors: errors
})
}
I would suggest separating the form's "field state", from your "validation state", to avoid potential conflicts in the case that you have a field with name "error".
If your form has a field with name "error", changing it's value will cause your validation state to be replaced, and will produce errors/unexpected results.
Consider making the following adjustments:
// in renderTextField() use this.state.form[field.name]
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state.form[field.name]}
/>
And in handleChange(event) consider revising it to:
handleChange(event) {
const form = { ...this.state.form, [event.target.name] : event.target.value }
this.setState({
form : form
})
}
Note, you will also need to initialise your component state to include/define the form object to track the state of fields.