I have an event app. Where a user comes add number of people. Then based on the number of people I show input fields for name and email. I am using a for loop to display fields for each used based on number of people.
<input
type="email"
name={`email`}
required
className="form-control"
placeholder="Enter"
onChange={(e) => {
onChange(e, i);
}}
/>
<input
type="text"
name={`firstname`}
required
className="form-control"
placeholder="Enter"
onChange={(e) => {
onChange(e, i);
}}
/>
I have a state:
const [fields, setFields] = useState([{ email: "", firstname: "" }]);
I am trying to update the state dynamically for each user. I am doing something like this:
const onChange = (e, index) => {
const updatedArray = [...fields];
updatedArray.push(([e.currentTarget.name] = e.currentTarget.value));
setFields(updatedArray);
};
Code isn't working. I am not getting a separate object for each user in state array. I am stuck and any help would be appreciated.
the onChange handler should be something like this:
const onChange = (e, index) => {
const inputName = e.currentTarget.name;
const inputValue = e.currentTarget.value;
setFields(prevFields =>
prevFields.map((pF, i) =>
i === index ? { ...pF, [inputName]: inputValue } : pF
)
);
};
functional update is recommended as the new state depends on the old state and the problem with your snippet is you're not using the index to figure out which object to update and the syntax for updating the property in that object is also incorrect.
the two inputs also need a value prop:
<input
type="email"
name={`email`}
required
className="form-control"
placeholder="Enter"
value={field.email}
onChange={(e) => {
onChange(e, i);
}}
/>
<input
type="text"
name={`firstname`}
required
className="form-control"
placeholder="Enter"
value={field.firstname}
onChange={(e) => {
onChange(e, i);
}}
/>
Update:
You need additional code for adding more users, if you want to add a user when a button is clicked:
const addUser = () => {
setFields(prevFields => [
...prevFields,
{
email: "",
firstname: ""
}
]);
};
<button onClick={addUser}>Add User</button>
Here's a React StackBlitz implementing the above solutions
Related
I am trying to let the user input a image that i later can display in another component, It is supposed to be displayed inside my "Card" component which is displayed inside my "Grid" component. The grid component then maps and displays all the houses in the "houses" array which also is a separate component. Manually creating houses inside the array works perfectly but when the user is trying to add a house everything but the images works. The console log in submitListing will show that the houses beeing added does contain an image but it is not beeing displayed in the grid. (Also, the first house that is submitted only contains an empty array for mainImg but the rest contains the "blob" image) Entire project: https://codesandbox.io/s/relaxed-orla-fqcwoy?file=/src/components/header/AddListing.jsx
HTML
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
></textarea>
<input
onChange={handleInputChange}
name="mainImg"
type={"file"}
></input>
JS
const [listing, setListing] = useState({
area: "",
mainImg: [],
});
function handleChange(e) {
const { name, value } = e.target;
setListing((previousListing) => {
return { ...previousListing, [name]: value };
});
}
const [file, setFile] = useState();
function handleInputChange(e) {
setFile(URL.createObjectURL(e.target.files[0]));
}
function submitListing(e) {
e.preventDefault();
setListing({
area: "",
mainImg: file,
});
houses.push(listing);
console.log(houses);
}
So, here are the issues. I might forget something but i forked codesandbox, so you will be able to compare.
In your houses.js and AddListing.jsx you had mainImage and mainImg. You need to have a strict structure and naming, they had to match.
const [file, setFile] was.. useless, you had to save blobs into the listing object itself in the mainImage, img2, etc.
About the structure again. You had your mainImg, img2, ... set to an empty array, []. That have no sense due to you dont use <input type="file" multiple>. Switched them to undefined / string.
You cannot really bind value to the input type=file in react. Just remove the value from inputs.
Be careful with useEffects and useState's setValues, without understanding how they work it is really easy to make a mistake.
Here is your updated and working AddListing.jsx
import React, { useState, useCallback, useEffect } from "react";
import "./AddListing.css";
import houses from "../../houses";
const getDefaultListing = () => {
return {
id: houses.length + 1,
area: "",
state: "",
shortDesc: "",
longDesc: "",
pricePerNight: "",
cleaningFee: "",
serviceFee: "",
mainImage: undefined,
img2: undefined,
img3: undefined,
img4: undefined,
img5: undefined
};
};
function AddListing() {
const [listing, setListing] = useState(() => getDefaultListing());
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setListing((prev) => ({ ...prev, [name]: value }));
}, []);
const handleInputChange = useCallback((e) => {
const { name, files } = e.target;
const [singleImage] = files;
if (!singleImage) {
setListing((prev) => ({ ...prev, [name]: undefined }));
} else {
const blob = URL.createObjectURL(singleImage);
setListing((prev) => ({ ...prev, [name]: blob }));
}
}, []);
const submitListing = useCallback(
(e) => {
e.preventDefault();
houses.push(listing);
setListing(() => getDefaultListing());
console.log(houses);
},
[listing]
);
useEffect(() => {
console.log(listing);
}, [listing]);
return (
<div className="add-listing-div">
<form>
<p id="p1">State</p>
<p id="p2">Area</p>
<textarea
value={listing.state}
name="state"
onChange={handleChange}
className="state"
/>
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
/>
<p id="p3">Short Description</p>
<textarea
value={listing.shortDesc}
name="shortDesc"
onChange={handleChange}
className="short-description"
/>
<p id="p4">Long Description</p>
<textarea
value={listing.longDesc}
name="longDesc"
onChange={handleChange}
className="long-description"
/>
<p id="p5">Price/Night</p>
<p id="p6">Cleaning Fee</p>
<p id="p7">Service Fee</p>
<textarea
value={listing.pricePerNight}
name="pricePerNight"
onChange={handleChange}
className="price-per-night"
/>
<textarea
value={listing.cleaningFee}
name="cleaningFee"
onChange={handleChange}
className="cleaning-fee"
/>
<textarea
value={listing.serviceFee}
name="serviceFee"
onChange={handleChange}
className="service-fee"
/>
<label>Main Image: </label>
<input onChange={handleInputChange} name="mainImage" type="file" />
<label>Second Image: </label>
<input onChange={handleInputChange} name="img2" type="file" />
<label>Third Image: </label>
<input onChange={handleInputChange} name="img3" type="file" />
<label>Fourth Image: </label>
<input onChange={handleInputChange} name="img4" type="file" />
<label>Fifth Image: </label>
<input onChange={handleInputChange} name="img5" type="file" />
<button onClick={submitListing}>Submit</button>
</form>
</div>
);
}
export default AddListing;
I have created a form with multiple input there two dropdown first has option and second one's option changes dynamically based on selection of first dropdown. also i am storing all values in state with submit event.
Okay problem is i am either able to to get values and in state on submit event or either able to dynamically change the option for second dropdown.
But when I am trying to do both or trying to run two function on same onChange event its not trigging is any one can help me out why its not getting trigger and what is the issue.
link : if any one want to see code it dummy code
https://codesandbox.io/s/clever-mendel-ozt8tu?file=/src/App.js:0-1748
App.js
import "./styles.css";
import { useState } from "react";
function App() {
const data = {
first: ["car", "bus"],
second: ["bike", "train"],
third: ["plane", "rocket"]
};
const [option, setOption] = useState([]);
const getInput = (e) => {
let input = e.target.value;
setOption(data[input]);
};
const [values, setValues] = useState({
name: "",
city: "",
category: "",
ride: ""
});
const inputHandle = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(values);
};
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="name"
name="name"
value={values.name}
onChange={inputHandle}
/>
<input
type="text"
placeholder="city"
name="city"
value={values.city}
onChange={inputHandle}
/>
<br />
<select
name="category"
values={values.category}
onChange={(e) => {
getInput();
inputHandle();
}}
>
<option defaultValue="category">category</option>
<option value="first">first</option>
<option value="second">Second</option>
<option value="third">Third</option>
</select>
<select name="ride" value={values.ride} onChange={inputHandle}>
{option.map((ele) => (
<option value={ele}>{ele}</option>
))}
</select>
<br />
<button type="submit">Submit</button>
</form>
</>
);
}
export default App;
For the handler of the onChange in the first select, you are taking in the event object e as parameter but not passing it forward in getInput() or inputHandle(). Therefore, you should write something like this:
onChange={(e) => {
getInput(e);
inputHandle(e);
}}
This should do the job. Also, try to use only one function in the handler, like the one you have used for submitting the form.
If you want to combine everything into one handler, you can use the following handler:
const categoryHandler = (event) => {
const selectName = event.target.name;
const selectValue = event.target.value;
setOption(data[selectValue]);
setValues({
...values,
selectName: selectValue
});
};
My React App has over 20 fields on form tag. and my working code like the following
function DriverForm() {
const [formData1, setFormData1] = useState(1);
const [formData2, setFormData2] = useState(2);
const [formData3, setFormData3] = useState(3);
const [formData4, setFormData4] = useState(4);
const [formData5, setFormData5] = useState(5);
const handleSubmit = (event) => {
event.preventDefault();
};
return (
<div className="wrapper">
<form onSubmit={handleSubmit}>
<input value={formData1} onChange={(e) => setFormData1(e.target.value)} />
<input value={formData2} onChange={(e) => setFormData2(e.target.value)} />
<input value={formData3} onChange={(e) => setFormData3(e.target.value)} />
<input value={formData4} onChange={(e) => setFormData4(e.target.value)} />
<input value={formData5} onChange={(e) => setFormData5(e.target.value)} />
<button type="submit">Submit</button>
</form>
</div>
);
}
as you see, my code has the code repeat to declare the input field state variable like const [formData5, setFormData5] = useState(5);
and At the rendering function, onChange={(e) => setFormData1(e.target.value)} was repeated lots of time.
is there any way to be simple and look professional by using json object and other?
I suppose you could make the code more DRY by combining the duplicated bits.
Single state object holding field values by input name
Single change handler to handle merging in state updates
Assign inputs a name attribute to be passed with change event
DRY solution
function DriverForm() {
const [formData, setFormData] = useState({
data1: 1,
data2: 2,
// etc...
});
const handleSubmit = (event) => {
event.preventDefault();
// handle formData
};
const handleChange = event => {
event.preventDefault();
const { name, value } = event.target;
setFormData(formData => ({
...formData,
[name]: value,
}))
}
return (
<div className="wrapper">
<form onSubmit={handleSubmit}>
<input value={formData.data1} name="data1" onChange={handleChange} />
<input value={formData.data2} name="data2" onChange={handleChange} />
// etc..
<button type="submit">Submit</button>
</form>
</div>
);
}
If you know ahead of time what the input types will be you can load them into a configuration array and map them
const fieldData = [
{
name: 'data1',
type: 'text',
},
// etc...
];
...
<form onSubmit={handleSubmit}>
{fieldData.map(({ name, type }) => (
<input
key={name{
type={type}
value={formData[name]}
name={name}
onChange={handleChange}
/>
))}
<button type="submit">Submit</button>
</form>
There are many answers to this question. However, one of the rules I like to code by is 'Everything is an array'.
I would start by creating an array of form fields. Then take those form fields, and build the initial state object to be used in the formData state hook.
const formFields = [
{ name: 'field1', type: 'text', initialValue: '1' },
{ name: 'field2', type: 'text', initialValue: '2' },
{ name: 'field3', type: 'text', initialValue: '3' },
{ name: 'field4', type: 'text', initialValue: '4' },
{ name: 'field5', type: 'text', initialValue: '5' },
];
const initalState = formFields.reduce(
(acc, next) => ({ ...acc, [next.name]: next.initalValue }),
{}
);
Then create a single handleChange handler.
const handleChange = (name, value) => {
setFormData({
...formData,
[name]: value,
});
};
And finally, map the formFields in your render function.
{formFields.map(({ name, type }) => (
<input
key={name}
name={name}
type={type}
value={formData[name]}
onChange={(e) => handleChange(e.target.value, name)}
/>
))}
Complete code:
const formFields = [
{ name: 'field1', type: 'text', initialValue: '1' },
{ name: 'field2', type: 'text', initialValue: '2' },
{ name: 'field3', type: 'text', initialValue: '3' },
{ name: 'field4', type: 'text', initialValue: '4' },
{ name: 'field5', type: 'text', initialValue: '5' },
];
const initalState = formFields.reduce(
(acc, next) => ({ ...acc, [next.name]: next.initalValue }),
{}
);
function DriverForm() {
const [formData, setFormData] = useState(initalState);
const handleChange = (name, value) => {
setFormData({
...formData,
[name]: value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
};
return (
<div className="wrapper">
<form onSubmit={handleSubmit}>
{formFields.map(({ name, type }) => (
<input
key={name}
name={name}
type={type}
value={formData[name]}
onChange={(e) => handleChange(e.target.value, name)}
/>
))}
<button type="submit">Submit</button>
</form>
</div>
);
}
Working with forms in react is one of the most challenging problems to solve when developing applications. The best way to work with the form is react-hook-form. It is introduced so that the performance or latency of the react application will be improved to such a great extend. There is another way of creating a form in react is Formik.
Both Formik and React Hook Form are solving the same problem (provides a way to store form data in local state), but React Hook Form is a way of creating a form with uncontrolled components and the hooks allow it to give a better performance result than Formik.
I'll give you one simple example of react-hook-form.
import React, {useState} from "react";
import { useForm } from "react-hook-form";
function App() {
const { register, handleSubmit } = useForm();
const {data, setData} = useState({});
const onSubmit = (formData) => {
console.log(formData); // an object of form Data
setData(formData);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="formInput1" ref={register} />
<input name="formInput2" ref={register} />
<input type="submit" />
</form>
);
}
When we console log the form data we get an object of form input.
>console
object{
formInput1: "your value",
formInput2: "your 2nd value"
}
You can store this object in a local state or in a database through API call.
I'll recommend you to learn both Formik and React hook form then you will be able to differentiate between them. Yes, react-hook-form is the best way to create a form in react in terms of performance. Formik lets you easy to understand and create a form in one go.
Others have given the best example of creating a react form without using formik and react hook form.
import React, { useState } from 'react'
const App=()=>{
const[fullname, setFullName]=useState({
fname:"",
lname:"",
email:"",
phone:""
});
const inputEvent=(event)=>{
// const value = event.target.value;
// const name = event.target.name;
const {name,value}=event.target;
setFullName((preValue)=>{
console.log(preValue)
console.log(value)
return{
...preValue,//gets the previous value
[name]:value//sets the value to the field that is getting changed
}
})
}
const submits=(event)=>{
event.preventDefault();
alert('Form has been submitted')
}
return(
<>
<div>
<form onSubmit={submits}>
<div>
<h1>Hello {fullname.fname} {fullname.lname}</h1>
<div className='container'>
<p>{fullname.email}</p>
</div>
{/* <p>{fullname.phone}</p> */}
<input type='text'
placeholder='Enter Your Name'
onChange={inputEvent}
name='fname'
value={fullname.fname}
/>
<input type='text'
placeholder='Enter Your Last Name'
onChange={inputEvent}
name='lname'
value={fullname.lname}
/>
<input type='text'
placeholder='Enter Your Email'
onChange={inputEvent}
name='email'
value={fullname.email}
/>
<input type='phone'
placeholder='Enter Your Phone Number'
onChange={inputEvent}
name='phone'
value={fullname.phone}
/>
<button type='submit'>Submit</button>
</div>
</form>
</div>
</>
)
};
export default App;
This how handle too much of inputs in a form you can extend it in your desired way
What you should do is just replace the names and values in your way like formData1
I am practicing REST API by using one Fake API site. For front-end, I am using React typescript and React router dom for routing. I successfully login the email and password by using Fake API's login and redirect to list users, where I fetched the data from Fake API and shows the user's name, image. I used the edit button, after clicking the button it will redirect to my Update components where it will populate the input field then I will update the data. My update components work fine as expected but in my console, I am getting a warning as soon as I type my input field.Here is the Error visualization
This is React Update components
import React, { useState, useEffect } from "react";
import axios from "axios";
const Update = props => {
const [state, setState] = useState({
first_name: "",
last_name: "",
email: ""
});
const [loading, setLoading] = useState(false);
useEffect(() => {
axios
.get("https://reqres.in/api/users/" + props.match.params.id)
.then(response => {
setState({
first_name: response.data.data.first_name,
last_name: response.data.data.last_name,
email: response.data.data.email
});
})
.catch(function(error) {
console.log(error);
});
}, [props.match.params.id]);
const onChangeFirstName = e => {
setState({
first_name: e.target.value
});
};
const onChangeLastName = e => {
setState({
last_name: e.target.value
});
};
const onChangeEmail = e => {
setState({
email: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
setLoading(true);
const obj = {
first_name: state.first_name,
last_name: state.last_name,
email: state.email
};
axios
.patch("https://reqres.in/api/users/" + props.match.params.id, obj)
.then(res => console.log(res.data));
setLoading(false);
props.history.push("/users");
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>First Name: </label>
<input
type="text"
className="form-control"
value={state.first_name}
onChange={onChangeFirstName}
id="first_name"
/>
</div>
<div className="form-group">
<label>Last Name: </label>
<input
type="text"
className="form-control"
value={state.last_name}
onChange={onChangeLastName}
id="last_name"
/>
</div>
<div className="form-group">
<label>Email: </label>
<input
type="email"
className="form-control"
value={state.email}
onChange={onChangeEmail}
id="email"
/>
</div>
<div className="form-group">
<button
className="btn waves-effect blue lighten-1"
type="submit"
name="action"
disabled={loading}
>
{loading ? "loading..." : "save"}
</button>
</div>
</form>
</div>
);
};
export default Update;
With hooks, when you set the state of an object, you need to merge all the properties by yourself. In other words, if you update a property of an object with state updater, the remaining properties of the objects are not merged by themselves unlike this.setState in class components.
Modify your onChange to like this:
const onChangeFirstName = e => {
const val = e.target.value;
setState(prevState => ({
...prevState,
first_name: val
}));
};
See working demo
Also quick suggestion:
Instead of writing multiple onChanges, you can simplify and just use one.
Like this:
<input
type="text"
className="form-control"
value={state.first_name}
onChange={onChange}
id="first_name"
name="first_name" />
...
const onChange = e => {
const {name, value} = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
I'm trying to empty the input tag once I'm updating my state:
state = {
formName: '',
inputs: [],
tempInput: {
inputLabel: '',
inputType: '',
inputValue: ''
}
};
this is my form:
<div className="formG">
<form className="form-maker" onSubmit={this.handleSubmit}>
Label:
<input name="inputLabel" type="text" onChange={this.handleChange} />
Type:
<input name="inputType" type="text" onChange={this.handleChange} />
Value
<input name="inputValue" type="text" onChange={this.handleChange} />
Form Name
<input name="formName" type="text" onChange={this.formName} />
<button>Submit</button>
</form>
that's how I handle the change
handleChange = e => {
const { name, value } = e.target;
this.setState(currentState => ({
tempInput: { ...currentState.tempInput, [name]: value }
}));
};
and I tried to just empty the tempInput but it doesn't work, anybody knows why?
handleSubmit = e => {
e.preventDefault();
const inputs = [...this.state.inputs, this.state.tempInput];
const { tempInput } = this.state;
tempInput.inputLabel = '';
tempInput.inputType = '';
tempInput.inputValue = '';
this.setState({ inputs, tempInput });
};
Your form is an uncontrolled component, so they are not controlled by the state fields. That's why your approach didn't work. Instead you can do e.target.reset() which will clear the entire form. But if you want to reset some input, you can access them and set the .value to "" as I had shown below.
An uncontrolled component works like form elements do outside of React. When a user inputs data into a form field (an input box, dropdown, etc) the updated information is reflected without React needing to do anything. However, this also means that you can’t force the field to have a certain value. From Doc
So your handleSubmit method will look like:
handleSubmit = e => {
e.preventDefault();
const inputs = [...this.state.inputs, this.state.tempInput];
// ....
// The below will reset entire form.
// e.target.reset();
// If you want some of them to empty.
const { elements } = e.target
elements['inputLabel'].value = "";
elements['inputType'].value = "";
elements['inputValue'].value = "";
};
Check the doc of HTMLFormElement.elements
Your input tags are not displaying the value of your state.
1) pull the individual values out of tempInput
2) use the value stored in your state that is then updated by your handleChange.
3) In your handleSubmit function reset your individual values to and empty string.
your handleChange should look like:
handleChange = e => {
const { name, value } = e.target;
this.setState([name]: value);
};
your jsx should look like :
<form className="form-maker" onSubmit={this.handleSubmit}>
Label:
<input name="inputLabel" value={this.state.inputLabel} type="text" onChange={this.handleChange} />
Type:
<input name="inputType" value={this.state.inputType} type="text" onChange={this.handleChange} />
Value
<input name="inputValue" value={this.state.inputType} type="text" onChange={this.handleChange} />
Form Name
<input name="formName" value={this.state.formName} type="text" onChange={this.formName} />
<button>Submit</button>
</form>
You're mutating the original state. You can copy and then only set the state. Just changing the following will work fine for you.
Replace this:
const { tempInput } = this.state;
With this:
const { tempInput } = {...this.state}; // copy the state
Also, be sure to bind the state value in your input elements like this to make them controlled component:
<input name="inputLabel" type="text"
onChange={this.handleChange}
value={this.state.tempInput.inputLabel || ''} />
And your handler should be:
handleChange = e => {
const { value } = e.target;
this.setState({value});
// now, value will correspond to the controlled component
};
Also take care react suggest to use controlled component as far as possible:
In most cases, we recommend using controlled components to implement forms.