help please I want to add value to state without overwriting. Currently, when adding a value, the array is overwritten. I want to use useState and I want to use the value from the form.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const handleSubmit = (e) => {
e.preventDefault();
}
const handleChange2 = (e) => {
setNames({
...names,
people: [{[e.target.name]: e.target.value}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={names.email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>`enter code here`
) }
export default StateModification;
I think you need to add an email in your data and after click on add button that email will store in people variable with your previous data so i have update your code and it should work for you.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const [email,setEmail] = useState("")
const handleSubmit = (e) => {
e.preventDefault();
setNames({
people: [...names.people, { email }]
})
}
const handleChange2 = (e) => {
e.preventDefault();
setEmail(e.target.value)
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>
) }
export default StateModification;
Related
Below are 2 files that is expected to display details in a form, name, service, date, cost. The problem is that it doesn't display information entered in other input fields when I choose a future date. Whereas when I use the current date, it displays the information entered in other input fields as expected. Why is this the case please and how do i fix it?
import { useState, useEffect } from 'react';
import axios from 'axios';
const ConfirmBooking = () => {
//track state
const [data,setData] = useState([])
const Style = {
color: 'rgb(97, 113, 154)',
padding: '5px'
}
//GET data
useEffect(() => {
axios
.get('http://localhost:5000/api/bookings')
.then(res => {
console.log(res)
setData(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
//DELETE data
const deleteHandler =(id) =>{
axios
.delete('http://localhost:5000/api/bookings/'+id)
.then(res => {
console.log(res.data);
}
)
.catch(error =>{
console.log(error)
})
}
if(!data?.length) return <div>loading...</div>
return (
<div className='bookings'>
<h4 style={Style}>Name:{" "}{data.at(-1).name}</h4>
<h4 style={Style} >Service:{" "}{data.at(-1).service}</h4>
<h4 style={Style} >Date:{" "}{data.at(-1).date}</h4>
<h4 style={Style} >Cost:{" "}{data.at(-1).cost}</h4><br></br>
<button className='Btn'>Edit</button>
<button className='Btn' onClick={ () => deleteHandler(data.at(-1))} >Delete</button>
</div>
)
}
export default ConfirmBooking;
import React, { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom'
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
const Form = () => {
const navigate = useNavigate();
const [myState, setMyState] = useState({
name: "",
service: "finance",
date: new Date(),
cost: "3$"
});
//event to handle all inputs except datepicker
const handleChange = (e)=> {
// const { name, value} = e.target;
const name = e.target.name;
const value = e.target.value
//to update the input myState
setMyState
({...myState, [name]: value });
}
const handleDateChange = (date) => {
setMyState({
date:date
})
}
const handleSubmit = (e) => {
e.preventDefault();
if (myState !== "") {
alert('booking success')
}
//Add data to database
axios.post('http://localhost:5000/api/bookings', myState)
.then(res => {
setMyState
(res.data);
console.log(res);
//redirect to another page
navigate('/ConfirmBooking')
})
.catch((error) => {
console.log(error)
})
}
return (
<form className='form' onSubmit={handleSubmit} >
<h2 className="headerForm">Create appointment</h2>
<div className="mb-3">
<label className="form-label">Name</label>
<input name='name' type="text" className="form-control" id="exampleFormControlInput1" value={myState.name} onChange={handleChange} />
<label className="form-label">Service</label>
<input name='service' type="text" className="form-control " id="exampleFormControlInput1" value={myState.service} onChange={handleChange} />
<label className="form-label"> Date</label>
<div>
<DatePicker
selected={myState.date}
onChange={handleDateChange}
startDate = {new Date()}
minDate={new Date()}
filterDate={date => date.getDay() !== 6 && date.getDay() !== 0}
/>
</div>
<label className="form-label">Cost</label>
<input name='cost' type="text" className="form-control" id="exampleFormControlInput1" value={myState.cost} onChange={handleChange} />
</div>
<button >Submit</button>
</form>
)
}
export default Form;
This method is changing the state without other properties:
...
const handleDateChange = (date) => {
setMyState({
date:date
})
}
...
If you want to change this property from state, you need to destructure previous value and change date.
const handleDateChange = (date) => {
setMyState({
...myState,
date:date
})
}
Input Array
Form validation for password Matching to Show Error msg if paassword and confirm password not matched
const inputs =[
{
id:1,
name:"username",
type:"text",
placeholder:"username",
errorMsg:"username should be 3-16 characters and shouldn't include special character",
label:"Username",
pattern:`^[A-Za-z0-9]{3,16}$`,
required:true
},
{
id:2,
name:"email",
type:"email",
placeholder:"email",
errorMsg:"provide an valid email address",
label:"Email",
required:true
},
{
id:3,
name:"firstname",
type:"text",
placeholder:"first name",
errorMsg:"",
label:"Firstname"
},
{
id:4,
name:"lastname",
type:"text",
placeholder:"last name",
errorMsg:"",
label:"Lastname"
},
{
id:5,
name:"password",
type:"passwod",
placeholder:"password",
errorMsg:"password should be 8-20 characters and must contain 1 number 1 special character and upper case letter",
label:"Password",
pattern:`^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!##$%^&*])[a-zA-Z0-9!##$%^&*].{8,20}$`,
required:true
},
{
id:6,
name:"confirmPassword",
type:"passwod",
placeholder:"confirm password",
errorMsg:"password does not matched",
label:"Confirm password",
// pattern:values.password,
required:true
}
]
export default inputs
form component
import React, {useState } from 'react'
import FormInput from '../../components/formInput/FormInput'
import './form.css'
import inputs from '../../components/formInput/input'
const Form = () => {
const [values,setValues]=useState({
username:"",
email:"",
firstname:"",
lastname:"",
password:"",
confirmPassword:"",
});
const handleChange=(e)=>{
setValues({...values,[e.target.name]:e.target.value})
}
console.log(values)
return (
<div className='container'>
<form className='form'>
<h1>Register Form</h1>
{inputs.map(input=>(<FormInput key={input.id} {...input} value={values[input.name]} onChange={handleChange} />))}
<button>Register</button>
</form>
</div>
)
}
export default Form
Input Component
import React from 'react'
import './formInput.css'
const FormInput = (props) => {
const {label,errorMsg,onChange,id,...inputprops} = props;
return (
<div className='formInput'>
<label>{label}:</label>
<input {...inputprops} onChange={onChange} />
<span>{errorMsg}</span>
</div>
)
}
export default FormInput
Here in Input Array I want to match password with Confirm Password but those are in state
How to Access them into Array input for Validation, The password has to Access in the input Array for matching as value pair for Pattern for id: 6
You can make validation on each character input. for eg. somethink like that:
const Form = () => {
const [values,setValues]=useState({
username:"",
email:"",
firstname:"",
lastname:"",
password:"",
confirmPassword:"",
});
const validatePasswordRepeat = e => {
return e.target.value === values.passowrd
}
const validate = (e) => {
if(e.name === "confirmPassword")
return validatePasswordRepeat(e);
return true;
}
const handleChange = (e) => {
if(!validate(e));
return; // or show validation message, set error etc
setValues({...values,[e.target.name]:e.target.value})
}
return (
<div className='container'>
<form className='form'>
<h1>Register Form</h1>
{inputs.map(input=>(<FormInput key={input.id} {...input} value={values[input.name]} onChange={handleChange} />))}
<button>Register</button>
</form>
</div>
)
or when user will try send the form. (better option)
const Form = () => {
const [values,setValues]=useState({
username:"",
email:"",
firstname:"",
lastname:"",
password:"",
confirmPassword:"",
});
const handleChange=(e)=>{
setValues({...values,[e.target.name]:e.target.value})
}
const validate = e => {
//make validation on form state in e (event)
}
const handleSubmit = useCallback((e) => {
if(!validate(e))
//show errores
// handle form send
}, [])
return (
<div className='container'>
<form className='form' onSubmint={handleSubmit}>
<h1>Register Form</h1>
{inputs.map(input=>(<FormInput key={input.id} {...input} value={values[input.name]} onChange={handleChange} />))}
<button>Register</button>
</form>
</div>
)
}
export default Form
I'm working on creating a dynamic input form, where I want to click on a button and get a pop-up asking for label name and input type(Eg: number or text). Here is a mock-up of what I want to create. I should be able to even remove these newly created label and input.
Once this is entered, it should create a new label and input form as below:
Any help will be greatly appreciated.
Looks like I'm doing someone else's work but...)))
A quick example so you know which way to go:
YourMainComponent.tsx:
import React, { useState } from "react";
import { DynamicForm } from "./dynamic-form";
export const Fields = () => {
const [getFields, setFields] = useState([]);
const addField = (field) => {
setFields((prevState) => [...prevState, field]);
};
return (
<>
<DynamicForm onSubmit={addField} />
{getFields &&
getFields.map((field, index) => (
<fieldset key={index}>
<label>{field.label}</label>
<input type={field.type} />
</fieldset>
))}
</>
);
};
YourDynamicFieldCreateComponent:
import React, { useState } from "react";
export const DynamicForm = ({ onSubmit }) => {
const [getField, setField] = useState({});
const formSubmit = (e) => {
e.preventDefault();
if (Object.keys(getField).length > 0) {
onSubmit(getField);
setField({});
}
};
const onFieldChanged = (e) => {
if (e.target.id === "label-field") {
setField((prevState) => ({
...prevState,
label: e.target.value
}));
} else if (e.target.id === "type-field") {
setField((prevState) => ({
...prevState,
type: e.target.value
}));
}
};
return (
<form onSubmit={formSubmit}>
<fieldset>
<label htmlFor="label-field">Your label </label>
<input
type="text"
id="label-field"
name="label-field"
onChange={onFieldChanged}
/>
</fieldset>
<fieldset>
<label htmlFor="type-field">Your type of field </label>
<input
type="text"
id="type-field"
name="type-field"
onChange={onFieldChanged}
/>
</fieldset>
<button>Add more</button>
</form>
);
};
Need add conditions and modals and other...
This is not production code, use this only for learning
here's my form
const AddSection = () => {
const { register, handleSubmit } = useForm();
const onSubmit = formData => {}
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select {...register("id", { required: true })}>
{
options.map(o => (
<option key={`o-${o.id}`} value={o.id}>{m.name}</option>
))
}
</select>
</form>
}
In onSubmit I'm trying to get select value and text
I tried the following. but that's not perfect as for me + it doesn't work properly in all the cases.
const AddSection = () => {
const { register, handleSubmit, setValue } = useForm();
register("text")
...
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select
{...register("id", { required: true })}
onChange={event => setValue("text", event.target[event.target.selectedIndex].text)}
>
...
</select>
</form>
}
Does anyone know the right way to do that ?
This works, it doesn't use the useForm hook but every time a new option is selected, it saves the value and text in the "user" state which is an object with the keys: value and text. Then console.log()s on submit.
import { useState } from "react";
const AddSection = () => {
const [user, setUser] = useState({});
let options = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Bob Builder" },
];
const handleSubmit = (e) => {
e.preventDefault();
console.log(user);
};
return (
<form onSubmit={handleSubmit}>
<select
required
onChange={(e) => {
setUser({
value: e.target.value,
text: e.target.options[e.target.selectedIndex].text,
});
}}
>
{options.map((o) => (
<option key={`o-${o.id}`} value={o.id}>
{o.name}
</option>
))}
</select>
<input type="submit" value="Submit" />
</form>
);
};
export default AddSection;
You did it the right way. Maybe
inside the onchange function, you can call another function onOptionSelect. The issue will probably be in passing values from the select component. So console it here and debug.
You can use react form Controller as below.
<Controller
control={control}
name="bank"
rules={{
required: 'Bank is required',
}}
render={({ onChange, name, value }) => (
<select
{...register("id", { required: true })}
onChange={(val: any) => {
onChange(val);
onOptionSelect(val);
}}
>
)}
/>
const onOptionSelect = (opt: any) => {
console.log(opt);
setValue('userId', opt._id);
setValue('username', opt.name);
};
You can also do it using a new useState variable as mentioned in #Benjamin Bialy's answer.
Refer https://react-hook-form.com/api/usecontroller/controller/
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import Multiselect from "./Multiselect.js";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const App = () => {
const { handleSubmit, setValue, control, watch, reset } = useForm({
mode: "onBlur",
reValidateMode: "onChange",
shouldUnregister: true,
defaultValues: {
disciplines: []
}
});
useEffect(() => {
(async () => {
await sleep(50);
setValue("disciplines", ["bbb", "ccc"], { shouldValidate: true });
reset({ disciplines: ["bbb", "ccc"] });
})();
}, []);
const values = watch();
return (
<form onSubmit={handleSubmit(console.log)}>
<Multiselect
name={"disciplines"}
label={"Disciplines"}
control={control}
values={["aaa", "bbb", "ccc"]}
/>
<button>submit</button>
<pre>{JSON.stringify(values, null, 4)}</pre>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can get select value by:-
const AddSection = () => {
const { register, handleSubmit } = useForm();
const onSubmit = formData => {alert(JSON.stringify(formData.id));}
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select {...register("id", { required: true })}>
{
options.map(o => (
<option key={`o-${o.id}`} value={o.id}>{o.name}</option>
))
}
</select>
</form>
}
After getting value if you can you can loop through options array to get the text also.
i am using react hook form to create a from with multiple pages
i am able to create it and it working with all filed except file-input-type how do i pass i file from another page and finaly pass it to api in the final page
i a have actualy 3 pages i have only added the 1st and final page (fist page has the file input filed and final page has the api to which it must be submitted)
form with file upload field
import { useForm } from "react-hook-form";
export default function Form(props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
<input style={styles.file} type="file" />
</div>
<input {...register("name", { required: true })}
name="husband"
value={props.getState("name")}
onChange={props.handleChange}
style={styles.input}
type="text"
placeholder="Name"
/>
<input onClick={handleSubmit(props.next)}
type="submit"
value="Next"
/>
form with submit button and api to which it must be uploaded
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const submitValue = (e) => {
// e.preventDefault();
props.state.patient = "true";
const data = props.state;
axios
.post("registration/", data)
.then(() => {
alert("updated data");
window.location = "/clogin";
})
.catch((error) => {
//var my_obj_str = JSON.stringify(error.response.data);
alert(JSON.stringify(error.response.data));
});
};
codesandbox
https://codesandbox.io/s/wizardly-worker-zicnr?file=/src/App.js
There are 2 options
Single form wraps all the steps
You can wrap the <Steps /> component with one form. Make the <Step />s components stateless that accepts onInputChange which will called upon input changes.
onInputChange call setValue to update the form's state.
When the form submitted, you have the file (among other inputs) so you can send it to the server.
import { useEffect } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, setValue } = useForm();
useEffect(() => {
register("myFile");
}, [register]);
const onInputChange = (e) => {
setValue(e.target.name, e.target.files[0]);
};
const onSubmit = (data) => {
alert(`Your file name: ${data.myFile.name}, size: ${data.myFile.size}`);
};
return (
<StepsProvider>
<form onSubmit={handleSubmit(onSubmit)}>
<MySteps onInputChange={onInputChange} />
</form>
</StepsProvider>
);
}
const MySteps = ({ onInputChange }) => {
const { next, prev } = useSteps();
return (
<Steps>
<div>
<h1>Step 1</h1>
<input type="file" name="myFile" onChange={onInputChange} />
<button onClick={next}>Next</button>
</div>
<div>
<h1>Step 2</h1>
<button>Submit</button>
</div>
</Steps>
);
};
https://codesandbox.io/s/gifted-wozniak-of14l?file=/src/App.js
Multiple forms in each step
If you want need to have a form inside each step, you can pass the step's data up to the parent when upon step's form submission. Still the parent has the form state so it can handle when all the steps completed
import { useRef } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const formState = useRef();
const onStepComplete = (data) => {
formState.current = {
...formState.current,
...data
};
};
const onComplete = (data) => {
onStepComplete(data);
const {
name,
myFile: [file]
} = formState.current;
alert(
`Your name: ${name} Your file name: ${file.name}, size: ${file.size}`
);
};
return (
<StepsProvider>
<MySteps onStepComplete={onStepComplete} onComplete={onComplete} />
</StepsProvider>
);
}
const MySteps = ({ onStepComplete, onComplete }) => {
return (
<Steps>
<Step1 onStepComplete={onStepComplete} />
<Step2 onComplete={onComplete} />
</Steps>
);
};
const Step1 = ({ onStepComplete }) => {
const { register, handleSubmit } = useForm();
const { next } = useSteps();
const onSubmit = (data) => {
onStepComplete(data);
next();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 1</h1>
<input type="file" {...register("myFile")} />
<button>Next</button>
</form>
);
};
const Step2 = ({ onComplete }) => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
onComplete(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 2</h1>
<input type="text" {...register("name")} />
<button>Submit</button>
</form>
);
};
https://codesandbox.io/s/condescending-leaf-6gzoj?file=/src/App.js
Passing down the function and tracking changes at parent level is not a great idea. react-hook-form provides a form context option which allows you to do this independently. Such that errors, onChange are handled in each step separately. But when you need to submit the data you can get all of those in the parent component.
Refer to this documentation: https://react-hook-form.com/api/useformcontext/
Note: Many people make the mistake of placing the FormProvider inside the parent component. Remember that FormProvider should wrap the Parent component as well.