tjinauyeung / svelte-forms-lib

📝. A lightweight library for managing forms in Svelte
https://svelte-forms-lib-sapper-docs.now.sh/
MIT License
603 stars 59 forks source link

Provide easier API for adding/removing fields in an array #63

Open TylerRick opened 4 years ago

TylerRick commented 4 years ago

Example of nice API: react-final-form-arrays

Coming to this library from react-final-form, there are a lot of things I miss from it. I really liked how clean, consistent, and extensible it was. One of the extensions to it was react-final-form-arrays (also see this article).

This provides a really, really nice API for adding and removing new elements to/from an array of fields:

        <FieldArray name="customers">
          {({ fields }) => (
            <div>
              {fields.map((name, index) => (
                <div key={name}>
                  <div>
                    <label>First Name</label>
                    <Field name={`${name}.firstName`} component="input" />
                  </div>
                  <div>
                    <label>Last Name</label>
                    <Field name={`${name}.lastName`} component="input" />
                  </div>
                  <button type="button" onClick={() => fields.remove(index)}>
                    Remove
                  </button>
                </div>
              ))}
              <button
                type="button"
                onClick={() => fields.push({ firstName: '', lastName: '' })}
              >
                Add
              </button>
            </div>
          )}
        </FieldArray>

Compared to current API

Compare that with the current recommended way to add/remove fields in an array, which is very verbose and boilerplatey:

    const add = () => {
      $form.users = $form.users.concat({ name: "", email: "" });
      $errors.users = $errors.users.concat({ name: "", email: "" });
    };

    const remove = i => () => {
      $form.users = $form.users.filter((u, j) => j !== i);
      $errors.users = $errors.users.filter((u, j) => j !== i);
    };

Not only is it boilerplatey, it exposes and requires knowledge of the internals of the library. And end-users of this library should not have to do all that work. And it's very easy to get one of those functions wrong. In fact, even this official example is not quite correct, because it fails to also modify $touched.users!

Bug in doc example

Here is a sandbox (based on https://svelte-forms-lib-sapper-docs.now.sh/array) showing how $touched.users should be modified too.

If you comment those 2 statements out and don't modify $touched.users when you add/remove a user, it creates a bug where if you add a new user, $isValid (initially true because it starts with 0 users) does not change from true to false. As soon as you add an empty user to the array, it should be considered invalid because name is a required field.

Related: a single store with form state instead of 3?

Ideally, I think it would be nice if we didn't have to try to keep 3+ objects ($form, $touched, $errors) in sync. Maybe there could be a centralized store of form data, keyed by field path, and under each field would be the state (value, touched, errors) for that field? I would highly recommend checking out how final-form manages state, since that is a very well-written library. (I haven't yet checked how Erik did it there but I would like to.)

Describe the solution you'd like

What I would like to see is a <FieldArray> component added to this library that works similarly to the one in react-final-form, and provides an API that is so enjoyable, simple, and easy to use that users literally can't mess up their add/remove functions like they can with the current API.

TylerRick commented 4 years ago

Well, I just discovered that there is a svelte-final-form project, so I will probably be abandoning svelte-forms-lib and switching to svelte-final-form... because final-form is awesome, and a more mature forms library, and what I had been wanting for svelte was a port of react-final-form anyway (I just didn't know one existed until today). :smile:

TylerRick commented 4 years ago

I wrote an implementation of FieldArray for svelte-forms-lib. I don't want to spend much more time on it since I'm probably going to switch to svelte-final-form, but I could probably push up a PR if anyone is interested.

Here's an example of how it looks to use it:

  <FieldArray name="users" let:fields>
    {#each fields.entries() as [name, i]}
      <div class="form-group">
        <div>
          <Field
            name={`${name}.name`}
            type="text"
            let:field
          >
            <input
              {...field.input}
              on:input={field['on:input']}
              on:blur={field.handleChange}
              placeholder={`Name of person #${i + 1}`}
            />
            {#if field.error}
              <small class="error">{field.error}</small>
            {/if}
          </Field>
        </div>

        <div>
          <Field
            name={`${name}.email`}
            type="email"
            let:field
          >
            <input
              {...field.input}
              on:input={field['on:input']}
              on:blur={field.handleChange}
              placeholder="E-mail address"
            />
            {#if field.error}
              <small class="error">{field.error}</small>
            {/if}
          </Field>
        </div>

        <button type="button" on:click={fields.remove(i)}>-</button>
      </div>
    {/each}
    <div class="button-group">
      <button type="button" on:click={() => fields.push({ name: "", email: "" })}>+</button>
      <button type="button" on:click={handleSubmit}>submit</button>
      <button type="button" on:click={handleReset}>reset</button>
    </div>
  </FieldArray>
larrybotha commented 4 years ago

@TylerRick never heard of Final Form, thanks for linking to the Svelte implementation - Final Form's approach sounds excellent!

If you've got something ready to push as a PR that'd be great :)

silentworks commented 3 years ago

@TylerRick I would be really interested in the PR for this if you still have the code available.