how to dynamically set an array object into input field in react - javascript

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,));

Related

Rendering array data with a condition

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)
}
/>
))}

How do you update a react whose questions are generated from an object?

I'm trying to create a form in react, where I have the questions pulled from a flask backend as a dictionary, where in React it results in an object. As a result, I have the following code when the component is getting rendered:
const [questions, setQuestions] = useState('');
const [answers, setAnswers] = useState('');
/// answers are pulled from api, with questions and answers set up to be equal.
...
{ Object.entries(questions).map(([key, question]) =>
(
<>
<p> {question}</p>
<textarea value = {answers[key]} onChange = {(e) => setAnswers(prevAnswer => ({...prevAnswer, key:e.target.value}))}
type = 'text' required />
</>
)
)
}
I can update the value with the keys, but I don't know how to accept a value to the associated key in the object.
Edit ** I found a relevant post from a while back...
How do you rerender a React component when an object's property is updated?
I believe this bit:
<textarea
value={answers[key]}
onChange={
(e) =>
setAnswers(prevAnswer => ({
...prevAnswer,
key: e.target.value
}))
}
type='text'
required
/>
is just going to set the literal key "key" to the new value. Instead we want to do this:
<textarea
value={answers[key]}
onChange={
(e) =>
setAnswers(prevAnswer => ({
...prevAnswer,
[key]: e.target.value // <-- note `[key]:` here rather than `key:`
}))
}
type='text'
required
/>

How to manage dynamic form data using useState?

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"

setState only setting last input when using object as state

Im trying to create a form with React. This form uses a custom Input component I created various times. In the parent form Im trying to get a complete object with all names and all values of the form:
{inputName: value, inputName2: value2, inputName3: value3}
For this, I created a 'component updated' hook, that calls the function property onNewValue to send the new value to the parent (two way data binding):
useEffect(() => {
if (onNewValue) onNewValue({ name, value });
}, [value]);
The parent form receives the data in the handleInputChange function:
export default () => {
const [values, setValues] = useState({});
const handleInputChange = ({
name,
value
}: {
name: string;
value: string | number;
}): void => {
console.log("handleInputChange", { name, value }); // All elements are logged here successfully
setValues({ ...values, [name]: value });
};
return (
<>
<form>
<Input
name={"nombre"}
required={true}
label={"Nombre"}
maxLength={30}
onNewValue={handleInputChange}
/>
<Input
name={"apellidos"}
required={true}
label={"Apellidos"}
maxLength={60}
onNewValue={handleInputChange}
/>
<Input
name={"telefono"}
required={true}
label={"Teléfono"}
maxLength={15}
onNewValue={handleInputChange}
/>
<Input
name={"codigoPostal"}
required={true}
label={"Código Postal"}
maxLength={5}
onNewValue={handleInputChange}
type={"number"}
/>
</form>
State of values: {JSON.stringify(values)}
</>
);
};
This way all elements from all inputs should be set on init:
{"codigoPostal":"","telefono":"","apellidos":"","nombre":""}
But for some reason only the last one is being set:
{"codigoPostal":""}
You can find the bug here:
https://codesandbox.io/s/react-typescript-vx5py
Thanks!
The set state process in React is an asynchronous process. Therefore even if the function is called, values has not updated the previous state just yet.
To fix, this you can use the functional version of setState which returns the previous state as it's first argument.
setValues(values=>({ ...values, [name]: value }));
useState() doesn't merge the states unlike this.setState() in a class.
So better off separate the fields into individual states.
const [nombre, setNombre] = useState("")
const [apellidos, setApellidos] = useState("")
// and so on
UPDATE:
Given setValue() is async use previous state during init.
setValues((prevState) => ({ ...prevState, [name]: value }));
The updated and fixed code, look at:
https://codesandbox.io/s/react-typescript-mm7by
look at:
const handleInputChange = ({
name,
value
}: {
name: string;
value: string | number;
}): void => {
console.log("handleInputChange", { name, value });
setValues(prevState => ({ ...prevState, [name]: value }));
};
const [ list, setList ] = useState( [ ] );
correct:
setList ( ( list ) => [ ...list, value ] )
avoid use:
setList( [ ...list, value ] )

Calling setState in callback of setState generate a weird bug that prevents input's value to be updated by onChange

I have a list of input to generate dynamically from an array of data I retrieve from an API.
I use .map() on the array to generate each of my input, and set value={this.state.items[i]} and the onChange property (with a modified handleChange to handle change on an array properly).
Now, I set in my constructor this.state = { items: [{}] }, but since I don't know how many items are going to be generate, value={this.state.items[i].value} crash since this.state.items[n] doesn't exist.
The solution is then to set each this.state.items[i] = {} (using Array.push for example) first, and then generate all the inputs.
var apiData = [{ value: "" }, { value: "" }]
this.setState({
items: apiData,
inputs: apiData.map((v, i) => {
return <input key={i} value={this.state.items[i].value}
onChange={(e) => this.handleChangeArray(e, i)} />
})
})
https://jsfiddle.net/qzb17dut/38/
The issue with this approach is that this.state.items doesn't exist yet on value={this.state.items[i].value} and we get the error Cannot read property 'value' of undefined.
Thankfully, setState() comes with a handy second argument that allows to do something only once the state is set. So I tried this:
var apiData = [{ value: "" }, { value: "" }]
this.setState({
items: apiData,
}, () => this.setState({
inputs: apiData.map((v, i) => {
return <input key={i} value={this.state.items[i].value}
onChange={(e) => this.handleChangeArray(e, i)} />
})
}))
https://jsfiddle.net/qzb17dut/39/
(Update: Please have a look at this example that better illustrate the use case: https://jsfiddle.net/jw81uo4y/1/)
Looks like everything should work now right? Well, for some reason, I am having this very weird bug were value= doesn't update anymore like when you forget to set onChange= on an input, but here onChange= is still called, value= is just not updated making the field remaining not editable.
You can see on the jsfiddle the problem for each method. The first one doesn't have the state set yet, which would allow the input to be edited, but crash because the state value was not yet set. And the second method fix the first issue but introduce this new weird bug.
Any idea about what I am doing wrong? Am I hitting the limit of react here? And do you have a better architecture for this use case? Thanks!
What about this approach instead, where you set the state of the API values only and then, generate the input based on the state from the render via Array.prototype.map like so
constructor (props) {
this.state = {items: []}
}
async componentDidMount(){
const apiData = await fetchApiData()
this.setState({items: apiData})
}
handleChange = (value, index) => {
const items = this.state.items;
items[index].value = value;
this.setState({ items });
};
updateState = () => {
const items = this.state.items;
items.push({value: ''}); // default entry on our item
this.setState({ items });
};
// here ur state items is exactly same structure as ur apiData
onSubmit =()=> {
console.log('this is apiData now', this.state.items)
}
render () {
<button onClick={this.updateState}>update state with inputs</button>
<button onClick={this.onSubmit}>Submit</button>
{this.state.items.map((item, index) => (
<input
key={index}
value={item.value}
onChange={e => this.handleChange(e.target.value, index)}
/>
))}
}
here is the codesandbox code for it
https://codesandbox.io/s/icy-forest-t942o?fontsize=14
with this, it will generate the input based on the items on the state, which in turns have the click handler which updates the state.
Well if I understand correctly, apiData is assigned to state.items and then also used to generate the inputs array. That means that for your purpose apiData and state.items are equivalent. Why don't you use the third map argument like:
var apiData = [{ value: "" }, { value: "" }]
this.setState({
items: apiData,
inputs: apiData.map((v, i, arr) => {
return <input key={i} value={arr[i].value}
onChange={(e) => this.handleChangeArray(e, i)} />
})
});
or the apiData array directly?

Categories