I have React component where I can dynamically add new text inputs. So, I need to push the values from the inputs to array.
Can anyone help me how to do this?
Here is my code:
function FormPage({ setData }) {
const [item, setItem] = useState([]);
const [counter, setCounter] = useState(0);
const handleCounter = () => {
setCounter(counter + 1);
};
const addItem = (setItem) => setItem((ing) => [...ing, newItem]);
return (
{Array.from(Array(counter)).map((c, index) =>
<TextField
key={index}
label="Item"
onChange={() => setItem(i=> [...i, (this.value)])}
/>
)}
<Button onClick={handleCounter}>Add one more item</Button>
)
}
Here is example in sandbox: https://codesandbox.io/s/solitary-sound-t2cfy?file=/src/App.js
Try this:
import "./styles.css";
import React, { useState } from "react";
export default function App() {
// Changes made here
const [item, setItem] = useState({});
const [counter, setCounter] = useState(0);
console.log("item 1:", item[0], "item 2:", item[1],item);
const handleCounter = () => {
setCounter(counter + 1);
};
const addItem = (newItem) => setItem((ing) => [...ing, newItem]);
return (
<>
{Array.from(Array(counter)).map((c, index) => (
<input
type="text"
key={index}
//Changes made here
value={item[index]}
label="Item"
// Changes made here
onChange={(event) => setItem({...item, [index]:event.target.value })}
/>
))}
<button onClick={handleCounter}>Add one more item</button>
</>
);
}
Instead of using an array to store the input values I recommend using an object as it's more straight-forward.
If you wanted to use an array you can replace the onChange event with the following:
onChange={(event) => {
const clonedArray = item.slice()
clonedArray[index] = event.target.value
setItem(clonedArray)
}}
It's slightly more convoluted and probably slightly less optimal, hence why I recommend using an object.
If you want to loop through the object later you can just use Object.entries() like so:
[...Object.entries(item)].map(([key, value]) => {console.log(key, value)})
Here's the documentation for Object.entries().
TextField component, so you also need to pass a value prop.TextField, we don't use this.value. Rather, the callback to onChange takes an argument of type Event and you can access the current value as follows<TextField
...
onChange={(e) => {
const value = e.target.value;
// Do something with value
}}
/>
TextField components at the same level, which is also causing an error. Try wrapping them in React.Fragment as follows...
return (
<React.Fragment>
{/* Here you can return multiple sibling components*/}
</React.Fragment>
);
TextField components using counter which is equal to the length of item array. In handleCounter, we'll add a placeholder string to accomodate the new TextField value....
const handleCounter = () => {
setCounter(prev => prev+1); // Increment the counter
setItem(prev => [...prev, ""]); // Add a new value placeholder for the newly added TextField
}
return (
<React.Fragment>
{ /* Render only when the value of counter and length of item array are the same */
counter === item.length && (Array.from(Array(counter).keys()).map((idx) => (
<TextField
key={idx}
value={item[idx]}
label="Item"
onChange={(e) => {
const val = e.target.value;
setItem(prev => {
const nprev = [...prev]
nprev[idx] = val;
return nprev;
})
}}
/>
)))}
<br />
<Button onClick={handleCounter}>Add one more item</Button>
</React.Fragment>
);
codeSolution: https://codesandbox.io/s/snowy-cache-dlnku?file=/src/App.js
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [item, setItem] = useState(["a", "b"]);
const handleCounter = () => {
console.log(item, "item");
setItem([...item, ""]);
};
const setInput = (index) => (evt) => {
item.splice(index, 1, evt.target.value);
setItem([...item]);
};
return (
<>
{item.map((c, index) => {
return (
<input
type="text"
key={index}
label="Item"
value={c}
onChange={setInput(index)}
/>
);
})}
<button onClick={handleCounter}>Add one more item</button>
</>
);
}
I have solved for you . check if this works for you , if any issues tell me