Dynamically add TextField to a form

I’m trying to create a form that allows adding more inputs dynamics. I put together a small test case that demonstrates my problem using a form that has an author and a book with a button to allow you to add N more books. The problem is that if I use TextField when I add more books the value is the same for all the inputs. If I change the value in one, all the others change to that value. If I simply change the TextField to input then it all works as expected. I looked at the documentation here Forms | RedwoodJS Docs looking for some insight but I didn’t see anything that might help. What am I missing about TextField?

import { useState } from 'react';

import { Form, FormError, Label, TextField, Submit } from '@redwoodjs/forms';

const BookFormPage = () => {
  const [formValues, setFormValues] = useState([{ title: '' }]);

  const handleChange = (index, event) => {
    let newFormValues = [...formValues];

    newFormValues[index][event.target.name] = event.target.value;
    setFormValues(newFormValues);
  };

  const addFormFields = () => {
    const newField = { title: '' };

    setFormValues([...formValues, newField]);
  };

  const removeFormFields = (index) => {
    let newFormValues = [...formValues];

    newFormValues.splice(index, 1);
    setFormValues(newFormValues);
  };

  const onSubmit = (form) => {
    console.log(form);
  };

  return (
    <>
      <MetaTags title="Home" description="Home page" />

      <h1>Form Relations</h1>
      <Form className="space-y-8" onSubmit={onSubmit}>
        <FormError
          error={error}
          titleClassName="font-semibold"
          wrapperClassName="bg-red-100 text-red-900 text-sm p-3 rounded"
        />
        <div className="space-y-6">
          <Label htmlFor="author">Author</Label>
          <div className="mt-1">
            <TextField name="author" />
          </div>
        </div>

        {formValues.map((element, index) => (
          <div className="space-y-6" key={index}>
            <Label htmlFor="title">Book</Label>
            <div className="mt-1">
             // if I change this to input it works
              <TextField
                name="title"
                value={element.title || ''}
                onChange={(event) => handleChange(index, event)}
              />
              {index ? (
                <button
                  type="button"
                  className="button remove"
                  onClick={() => removeFormFields(index)}>
                  Remove
                </button>
              ) : null}
            </div>
          </div>
        ))}
        <button
          className="py-2 px-4 mr-4"
          type="button"
          onClick={() => addFormFields()}>
          Add
        </button>

        <Submit className="py-2 px-4" disabled={loading} type="submit">
          Save
        </Submit>
      </Form>
    </>
  );
};

This might be of use.

Edited to add an official source of documentation:

1 Like

@TheFuture I’m not having a problem getting dynamic forms to work, the problem is getting them to work with the redwoodjs form components. If I replace the redwood form components for native elements everything works fine. Something is going on with the redwoodjs components that I don’t understand.

React-Hook-Form is Redwood’s form components library. Perhaps try going about your problem in the prescribed way using useFieldArray and just see if you have better results. It can’t hurt to try.

Are you changing the name property of the text field when you make a new one? If not, that might be a reason that all of the values are the same, not sure.

1 Like

Thank you @PantheRedEye ! I took some time away from this but I’ve confirmed your suspicion. That was indeed the problem. Here is a solution I came up with using your suggestion that works.

        {books.map((element, index) => {
          const book = `title-${index}`;

          return (
            <div className="space-y-6" key={index}>
              <Label htmlFor={book}>Book</Label>
              <div className="mt-1">
                <TextField
                  name={book}
                  value={books[index][book] || ''}
                  onChange={(event) => handleChange(index, event)}
                />
                {index ? (
                  <button
                    type="button"
                    className="button remove"
                    onClick={() => removeFormFields(index)}>
                    Remove
                  </button>
                ) : null}
              </div>
            </div>
          );
        })}
1 Like