Search code examples
javascriptreactjsformsreact-routerreact-router-dom

Dynamic input field using Form and actions in react-router-dom v6


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


Solution

  • 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: "" }));
    };
    

    Edit dynamic-input-field-using-form-and-actions-in-react-router-dom-v6

    enter image description here