I have a form that gets it's input fields dynamically, it can have hundreds of fields so i can't create a state individually, I was planning on doing something like using an object using the unique key of a form field but need some help.
Suppose the form has fields like this.
<form>
{inputFields.map((i) => {
<input type={i.type} />
})}
</form>
Now i would need a state like the one below
inputState = {
"INPUT_FIELD_NAME1": "INPUT FIELD VALUE 1",
"INPUT_FIELD_NAME2": "INPUT FIELD VALUE 2",
"INPUT_FIELD_NAME3": "INPUT FIELD VALUE 3",
}
I need help with this, how do i set values in such a manner in my input onChange and how do i access the values from the state and use them for the matching input field?
As per my understanding and knowledge, you have to update your dynamic structure like given as below
<form>
{inputFields.map((i) => (
<input
type={i.type}
name={`INPUT_FIELD_NAME${i.id}`}
onChange={handleChange}
/>
))}
</form>
Also have to update your react state on input change like
const [inputState, setinputState] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setinputState({
...inputState,
[name]: value
});
};
I hope it will work for you! Thanks.
I would do something like this:
const [form, setForm] = useState({})
const onChange = (event) => {
const { id, value } = event.target
setForm((prev) => ({ ...prev, [id]: value }))
}
<form>
{inputFields.map((i) => {
<input key={i.id} onChange={onChange} id={i.id} type={i.type} />
})}
</form>
Id must be unique, also you can use "name" property instead of "id"
Related
Short Explanation
I just want to get the data from the textboxes and send them to the server. In jquery I would just list out all my textboxes and get the value and form the json and send it to the server
Please see example code
https://codesandbox.io/s/xenodochial-euclid-3bl7tc
const EMPTY_VALUE = "";
const App = () => {
const [valueFirstName, setValueFirstName] = React.useState(EMPTY_VALUE);
const [valueMiddleName, setValueMiddleName] = React.useState(EMPTY_VALUE);
const [valueLastName, setValueLastName] = React.useState(EMPTY_VALUE);
return (
<div>
First Name:
<Work365TextBox value={valueFirstName} setValue={setValueFirstName} />
<div>{valueFirstName}</div>
Middle Name:
<Work365TextBox value={valueMiddleName} setValue={setValueMiddleName} />
<div>{valueMiddleName}</div>
Last Name:
<Work365TextBox value={valueLastName} setValue={setValueLastName} />
<div>{valueLastName}</div>
</div>
);
};
Problem
The current code has a label for first name, middle name, and last name and components to contain the textboxes and then the state of the component is stored in the parent.Then the parent takes the state and displays it below the textbox. So the solution works great. But the code is messy
Question
If I have a form that asks for 20 values what is a cleaner way to handle this ? I have tried to do this with by defining a object as json and calling a method when the value in each textbox changes but then I need to have a method for each textbox I have on the screen and it doesn't seem very clean. How would you solve this problem in a clean way ? Preferably I want to be able to have 50 textbox components but just call one method to update state.
the object solution you mentioned is a great way.
one thing to add is that you can pass a name prop to your input and then in the onChange method you can access it via event.target.name
that way you can dynamically update your object
something like this
const onChangeHandler = (event) => {
const name = event.target.name;
const value = event.target.value;
setState((prev) => ({ ...prev, [name]: value }));
};
another solution is to define it with useReducer but that will need a extra code.
I would just use an array like that:
// one item for each textbox
const textboxes = [
{
// human-readable label
label: "First name",
// unique key for data access
key: "firstName"
},
{
label: "Middle name",
key: "middleName"
},
{
label: "Last name",
key: "lastName"
}
];
A state like this:
const [values, setValues] = React.useState(
// converts array to record-like object
Object.fromEntries(textboxes.map((box) => [box.key, ""]))
);
const handleChange = (key, value) => {
setValues((values) => ({ ...values, [key]: value }));
};
And render it like this:
<div>
{textboxes.map((box) => (
<>
{box.label}:
<Work365TextBox
value={values[box.key]}
setValue={(value) => handleChange(box.key, value)}
/>
<div>{values[box.key]}</div>
</>
))}
</div>
Full example
I'm pre-populating an input field, using the data from an API. It's showing up just fine but I can't seem to edit it as it's not possible to edit the "value" field of an input field.
Here's what I want to do:
I want to pre-populate the input fields, edit the data inside them and then push the updated data back to the API.
For example, for the input field with the name Street Address, it's pre-populating the value from the API. In this case "Manhattan". I want to then change that value inside the input field to "New York" and send it back to the API, so that inside the JSON file it will update this specific value.
The left input field from the image is not pre-populating data from the API. Thus, it's empty.
Here's what I've got now.
function WarehousesDetailsEdit() {
const { id } = useParams();
const [warehouseData, setWarehouseData] = useState([]);
const [userInput, setUserInput] = useState([]);
const handleChange = (e) => {
setUserInput((recentInput) => ({ ...recentInput, [e.target.name]: e.target.value }));
};
useEffect(() => {
axios
.get(`http://localhost:8080/warehouses/${id}`)
.then((resp) => {
setWarehouseData(resp.data.warehouseDetails[0]);
})
.catch((err) => {
console.log(err, "Error!");
});
}, [id]);
return (
<>
// here, I'm able to update the input field as it's not coming from the API
<input
name="Warehouse Name"
value={userInput.WarehouseName}
onChange={handleChange}
/>
// here, the field was pre-populated using the API. Meaning, I can't update it.
<input
name="Street Address"
value={warehouseData.name}
onChange={handleChange}
/>
</>
)
I've not included the axios.put() in the code as it doesn't seem relevant right now as I just want to be able to update the pre-populated input field for now.
You're explicitly setting the value:
value={warehouseData.name}
And then nothing ever changes what's in warehouseData. Contrast that with the other <input> where the onChange event changes the value.
You can approach this in a couple of ways. Probably the simplest is to not have a warehouseData state at all. Just update the userInput state with the "pre-populated value". For example:
.then((resp) => {
setUserInput((recentInput) => ({ ...recentInput, name: resp.data.warehouseDetails[0].name }));
})
Then both <input> elements can just use userInput:
<input
name="Street Address"
value={userInput.name}
onChange={handleChange}
/>
Alternatively, if you want to keep both objects in their own separate state for some other reason, then you'd need to update that state. You can create a separate change handler for that:
const handleWarehouseDataChange = (e) => {
setWarehouseData((recentData) => ({ ...recentData, [e.target.name]: e.target.value }));
};
And use that in your <input>:
<input
name="Street Address"
value={warehouseData.name}
onChange={handleWarehouseDataChange}
/>
I need to make a large number of inputs and transfer this data to the server, I decided that the best solution would be to write all the options of these inputs into an array of objects, but I ran into the fact that I can’t get all my inputs to work. help me please
const test = [
{id: 1,state: 'city'},
{id: 2,state: 'language'},
{id: 3,state: 'brand'},
{id: 4,state: 'shop'},
]
const Auth = () => {
const [description, setDescription] = useState({city: "", language: "", brand: "", shop: ""});
const handleClick = async (event: any) => {
await store.update(description.city, description.brand);
};
const update = async (e: ChangeEvent<HTMLInputElement>) => {
setDescription({
...description,
city: e.target.value
});
};
return (
<>
{test.map(({ state, id}) => (
<TextField
key={id}
label={state}
id={state}
autoComplete="off"
variant="outlined"
className={styles.textFieldAuth}
helperText={state}
value={description.city}
onChange={update}
/>
))}
<Button
className={styles.saveButton}
variant="contained"
color="inherit"
id="login"
onClick={handleClick}
>
Save
</Button>
</>
)
}
You send to TextField description.city for every input. The correct props are like so:
<TextField
key={id}
label={state}
id={state}
autoComplete="off"
variant="outlined"
className={styles.textFieldAuth}
helperText={state}
value={description[state]}
onChange={update}
/>
See the change in the value prop.
Also, you only update city in the update function. You have to make it so that the update function adapts to what values you pass to it. If you pass the city then it should update the city, if the language then the language and so on.
Overall this is not a good way to implement inputs. I just suggest you do them one by one and send to each TextField its corresponding value and a separate setState for each one.
But just for the sake of the example. The way you can do it is by passing the state value to the Update function.
So your function will look like this:
const update = async (e: ChangeEvent<HTMLInputElement>, state) => {
setDescription((description) => {
...description,
[state]: e.target.value
});
};
Now you just need to make sure that in the TextField component when you call onChange, you pass to it the event e and state which you have received from props.
Note: If you want to use the value of a state variable in the setState itself, pass to it a callback function like I did in the setDescription
if you want to make it dynamic you would have to send the variable to save to your update method and retrieve your value with description[state]
<TextField
key={id}
label={state}
id={state}
autoComplete="off"
variant="outlined"
className={styles.textFieldAuth}
helperText={state}
value={description[state]}
onChange={(e)=>update(e, state)}
/>
const update = async (e: ChangeEvent<HTMLInputElement>, state) => {
setDescription({
...description,
[state]: e.target.value
});
};
I think first and foremost you need your configuration data to try and closely match the elements you're building. So instead of { id, state } use { id, type, name }.
(This may not have a huge effect on your example because you're specifically using a TextField component, but if you were using native HTML controls you could add in different input types like number, email, date etc, and your JSX could deal with it easily.)
Second, as I mentioned in the comments, you don't need for those functions to be async - for example, there's no "after" code in handleClick so there's no need to await anything.
So here's a working example based on your code. Note: I've stripped out the Typescript (because the snippet won't understand the syntax), and the references to the UI components you're using (because I don't know where they're from).
const { useState } = React;
// So, lets pass in out inputs config
function Example({ inputs }) {
// I've called the state "form" here as it's a little
// more meaningful
const [form, setForm] = useState({});
// `handleSave` is no longer `async`, and for the
// purposes of this example just logs the updated
// form state
function handleSave() {
console.log(form);
// store.update(form);
}
// Also no longer `async` `handleChange` destructures
// the name and value from the changed input, and updates
// the form state - a key wrapped with `[]` is a dynamic key
// which means you can use the value of `name` as the key value
function handleChange(e) {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
}
// In our JSX we destructure out the id, name, and
// type properties from each input object in the config
// and apply them to the various input element properties.
return (
<div>
{inputs.map(input => {
const { id, name, type } = input;
return (
<input
key={id}
type={type}
name={name}
placeholder={name}
value={form[name]}
onChange={handleChange}
/>
);
})}
<button onClick={handleSave}>Save</button>
</div>
);
}
// Our updated config data
const inputs = [
{ id: 1, type: 'text', name: 'city' },
{ id: 2, type: 'text', name: 'language' },
{ id: 3, type: 'text', name: 'brand' },
{ id: 4, type: 'text', name: 'shop' }
];
ReactDOM.render(
<Example inputs={inputs} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Pass both the key that you want to update and the value to the update function:
const update = (key: string, value: string) => {
setDescription({
...description,
[key]: value,
});
};
{test.map(({ state, id }) => (
<TextField
key={id}
label={state}
id={state}
autoComplete="off"
variant="outlined"
className={styles.textFieldAuth}
helperText={state}
value={description[state]}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
update(state, e.target.value)
}
/>
))}
i am using react.i have 2 inputs that by clicking a button dynamically ganerats.
this the code:
useEffect(() => {
const myFields = fieldsArray.map((field) => {
return (
<div key={field} className="col-12 m-auto d-flex">
<input id={field} name={`question${field}`} onChange={handleChangeFields} placeholder='سوال' className="form-control col-4 m-2" />
<input id={field} name={`answer${field}`} onChange={handleChangeFields} placeholder='جواب' className="form-control col-4 m-2" />
</div>
)
})
setFields(myFields)
}, [fieldsArray])
i need to save value of 2 inputs together in an opjects like this :
[{question : '',answer : ''}]
and the new 2 input's value are going to update the above array like this:
[{question : '',answer : ''}, {question : '',answer : ''}]
i can save each input's value separately like this :
const handleChangeFields = (e) => {
const { name, value } = e.target
setFieldsValue([...fieldsValue, { [name]: value }])
}
but i want each 2 input's value together
i need to save each 2 inputs together.
how do i do that?
This is a general example of how I think you can tie in what you're currently doing, and add in as little new code as possible. This logs the new data state when the button is clicked.
Have two states. One for collecting the updated form information (form), and another for the combined form data array (data).
Have a form with a single onChange listener (event delegation) so you can catch events from all the inputs as they bubble up the DOM. That listener calls the handleChange function which updates the form state.
Have a button with an onClick listener that calls handleClick which takes the current form state and adds it to the data state.
I would be a bit wary of storing JSX in state. You should have that map in the component return and only updating the actual field data with basic information.
One final issue - your inputs cannot have the same id. ids must be unique. I'd get rid of them altogether.
const { useEffect, useState } = React;
function Example() {
// Initialise two states. `data` is an array
// and `form` is an object
const [ data, setData ] = useState([]);
const [ form, setForm ] = useState({});
// Add the form data to the `data` array
function handleClick() {
setData([ ...data, form ]);
}
// We're using event delegation so we need to
// check what element we changed/clicked on
// - in this case the INPUT element. We can then
// update the form state with the name/value pair of that input
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Just logs the data state after a change
useEffect(() => console.log(data), [data]);
return (
<form onChange={handleChange}>
<input name="question" type="text" />
<input name="answer" type="text" />
<button
type="button"
onClick={handleClick}
>Update form
</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
this problem solved for me :
function App() {
const [results, setResults] = useState([{ question: "", answer: "" }])
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target
const list = [...results]
list[index][name] = value
setResults(list)
}
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...results]
list.splice(index, 1)
setResults(list)
}
// handle click event of the Add button
const handleAddClick = () => {
setResults([...results, { question: "", answer: "" }])
}
// handle submit to servers
const handlesubmit = () => {
// axios
console.log(results)
}
return (
<div className="App">
{results?.map((result, index) => {
return (
<div key={index}>
<input
name="question"
value={result.question}
onChange={(e) => handleInputChange(e, index)}
/>
<input
name="answer"
value={result.answer}
onChange={(e) => handleInputChange(e, index)}
/>
{results.length !== 1 && (
<button onClick={() => handleRemoveClick(index)}>
Remove
</button>
)}
{results.length - 1 === index && (
<button onClick={handleAddClick}>add</button>
)}
</div>
)
})}
<div style={{ marginTop: 20 }}>{JSON.stringify(results)}</div>
<button onClick={handlesubmit}>submit</button>
</div>
)
}
export default App
please i am trying to dynamically set array object into input field and send it to the backend. thanks
when i console.log printOut, it return undifined.
hi everyone, please i am trying to dynamically set array object into input field and send it to the backend. thanks
hi everyone, please i am trying to dynamically set array object into input field and send it to the backend. thanks
const myArr= [
{product: 'egg', price: 5, id:1},
{product: 'cake', price: 3, id:2}
]
const [input, setInput] = useState(myArr)
const changeHandler = (id) => event => {
const { name, value } = event.target;
setInput(input => input.map((el) => el.id === id
? {
...el,
[name]: value,
}
: el,
));
};
const submitForm = (e) =>{
e.preventDefault();
let printOut = input
console.log({print:printOut});
try {
axios.post('/products/print', printOut)
} catch (error) {
console.log(error);
}
}
return (
<form onSubmit={submitForm}>
{myArr.map(x=>(
<div key={x.id}>
<input name='product' value= {x.product} onChange={(e) =>changeHandler(x.id)(e)} />
<input name='price' value= {x.price} onChange={(e)=> changeHandler(x.id)(e)} />
</div>
))}
<input type="submit" value="Submit" />
</form>
)
As we discussed in the chat, there were plenty of issues.
handleChange call was not correct. Your onChange event should be onChange={(e) => changeHander(x.id)(e) } . Your changeHandler returns a function. I think it’s called currying but it’s a function returning another function.
Your setInput(input => input.map((el) => el.id === id? {...el, [name]: value,} : el,)); is also wrong. input.map will never work as you have set initial state for that as []. Now I don't know what you will need but, either update initial state to myArray or change setState mapping to myArray like setInput(input => myArray.map((el) => el.id === id? { ...el, [name]: value,} : el,));