I am new to using the new react router dom forms and actions. The normal forms that have static inputs are working fine? I want to know how can we make the input fields dynamic. Like for a specific field how to take multiple inputs for example I want to take interests from the user. Now the user can have multiple interests so he should have an option to add another interests field. basically, this is what I want to get from the user:
data={
name:'name goes here',
password:'password',
interest:["interest one","interest two", "interest three"]
}
Now the main thing is want to do it using Form and Actions of react-router-dom and FormData ApI. Here is my implementation till now:
import { Form } from "react-router-dom";
import { useState } from "react";
export const action = async ({ request }) => {
const formData = await request.formData();
console.log(formData);
const data = Object.fromEntries(formData);
console.log(data);
return data;
};
export default function Home() {
const [interests, setInterests] = useState([{ val: "" }]);
const handleFormChange = (event, index) => {
let data = [...interests];
data[index][event.target.name] = event.target.value;
setInterests(data);
};
const addFields = () => {
let object = {
name: "",
age: ""
};
setInterests([...interests, object]);
};
const removeFields = (index) => {
let data = [...interests];
data.splice(index, 1);
setInterests(data);
};
return (
<div>
<Form method="post" className="form">
<input type="text" name="username" placeholder="username" />
<input type="password" name="password" placeholder="password" />
{interests.map((item, index) => (
<div key={index}>
<input
type="text"
name="val"
placeholder="interests"
onChange={(event) => handleFormChange(event, index)}
value={item.val}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
))}
<button type="button" onClick={addFields}>
Add More..
</button>
<button type="submit"> Submit</button>
</Form>
</div>
);
}
but when I console the data I only get the name, password, and one value of interest. Also if I add another field and start to write in that field it shows this warning:
A component is changing an uncontrolled input to be controlled.
here is the code sandbox link also
https://codesandbox.io/s/naughty-chebyshev-yyznl3?file=/src/Home.jsx:0-1563
Give each "interests" input a unique name
attribute. Example, combine "interests" with the current array index.
const handleFormChange = (event, index) => {
setInterests((interests) =>
interests.map((interest, i) =>
i === index ? { val: event.target.value } : interest
)
);
};
{interests.map((item, index) => (
<div key={index}>
<input
type="text"
name={`interests-${index}`}
placeholder="interests"
onChange={(event) => handleFormChange(event, index)}
value={item.val}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
))}
If you would like to extract the interests back into an array you can map over the form data and search for the "interests-*"
keys and reduce the values into an array.
export const action = async ({ request }) => {
const formData = await request.formData();
const data = Object.fromEntries(formData);
const interests = Object.entries(data).reduce((interests, [key, value]) => {
const [interestsKey] = key.split("-");
if (interestsKey === "interests") {
interests.push(value);
}
return interests;
}, []);
...
return data;
};
Also if I add another field and start to write in that field it shows this warning: "A component is changing an uncontrolled input to be controlled."
This is because the interests
state that is mapped is expecting array element objects with a val
property, but the addFields
function adds objects with only name
and age
properties. Add val
as a defined property so new inputs have a defined value
prop.
const addFields = () => {
setInterests((interests) => interests.concat({ val: "" }));
};