formik: TypeError: `array.splice is not a function` with non-array errors

๐Ÿ› Bug report

Current Behavior

  • Create a form with a FieldArray.
  • setErrors to be of the form {"0": ['invalid']} (note that weโ€™re not using an array here).
  • When you remove the first item in the field array (using arrayHelpers.remove, it crashes with TypeError: a.splice is not a function

Expected behavior

No crash, should just work. Note that while I was testing, it seems it was working fine with 1.2.0.

Reproducible example

https://codesandbox.io/s/mjoknk1q2y

// Helper styles for demo
import "./helper.css";
import { MoreResources, DisplayFormikState } from "./helper";

import React from "react";
import { render } from "react-dom";
import { Formik, Form, FieldArray, Field, getIn } from "formik";
import * as Yup from "yup";

// To reproduce:
// 1. Click submit
// 2. Try to remove the first item
// 3. Observe the crash `array.slice is not a function`

const App = () => (
  <div>
    <h1>Friend List</h1>
    <Formik
      initialValues={{ friends: [{ name: "jared" }] }}
      onSubmit={(values, actions) => {
        // HERE, note that we set the error using an object - not an array.
        actions.setErrors({ friends: { "0": { name: ["invalid"] } } });
      }}
      render={({ values }) => (
        <Form>
          <FieldArray
            name="friends"
            render={arrayHelpers => (
              <div>
                {values.friends && values.friends.length > 0 ? (
                  values.friends.map((friend, index) => (
                    <div key={index}>
                      <Field name={`friends.${index}.name`}>
                        {({ field, form }) => {
                          const error = getIn(form.errors, field.name);
                          console.log(error);
                          console.log(field);
                          console.log(form.touched[field.name]);
                          return (
                            <div>
                              <input
                                type="text"
                                {...field}
                                placeholder="First Name"
                              />
                              {error && <div className="error">{error}</div>}
                            </div>
                          );
                        }}
                      </Field>
                      <button
                        type="button"
                        onClick={() => arrayHelpers.remove(index)} // remove a friend from the list
                      >
                        -
                      </button>
                      <button
                        type="button"
                        onClick={() => arrayHelpers.insert(index, "")} // insert an empty string at a position
                      >
                        +
                      </button>
                    </div>
                  ))
                ) : (
                  <button type="button" onClick={() => arrayHelpers.push("")}>
                    {/* show this when user has removed all friends from the list */}
                    Add a friend
                  </button>
                )}
                <div>
                  <button type="submit">Submit</button>
                </div>
              </div>
            )}
          />
        </Form>
      )}
    />
  </div>
);

render(<App />, document.getElementById("root"));

Suggested solution(s)

In my case, the back-end returns nested object errors using an object with numerical keys. This makes sense, because it allows it to report an error for the 125th object by just returning {friends: {124: ["invalid"]}}.

This looks like a regression - formik should allow that too.

Your environment

Software Version(s)
Formik 1.3.2
React 16.6.3
TypeScript N/A
Browser N/A
npm/Yarn N/A
Operating System N/A

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 13
  • Comments: 23 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Iโ€™m also hitting this error. In my case, itโ€™s only an issue after doing setFieldTouched(name, true) with a FieldArray.

Me too when i am trying setFieldTouched my fieldArray

@giubatt Yeah! I got that error too. Any update? This issue is quite a long time now.

Same error here with arrayHelpers.insert(index, value) when using Yup as a validator with Yup.array().min(1), which gives an error string, and then I get a copy.splice() is not a function, because copy is the error string coming from Yup

Also running into this error when trying to set errors as an object. (i.e. {โ€œ0โ€: {name: โ€œErrorโ€} }

I did a proof-of-concept fix in #1178 Iโ€™m not entirely happy with, @jaredpalmer would you mind having a look?

The problem is in https://github.com/jaredpalmer/formik/blob/master/src/FieldArray.tsx#L197

remove<T>(index: number): T {
    // We need to make sure we also remove relevant pieces of `touched` and `errors`
    let result: any;
    this.updateArrayField(
      // so this gets call 3 times
      (array?: any[]) => {
        const copy = array ? [...array] : [];
        if (!result) {
          result = copy[index];
        }
        if (isFunction(copy.splice)) {
          copy.splice(index, 1);
        }
        return copy;
      },
      true,
      true
    );

    return result;
  }

which compiles to:

FieldArrayInner.prototype.remove = function (index) {
        var result;
        this.updateArrayField(function (array) {
            var copy = array ? array.slice() : []; // ERROR HERE
            if (!result) {
                result = copy[index];
            }
            if (isFunction(copy.splice)) {
                copy.splice(index, 1);
            }
            return copy;
        }, true, true);
        return result;
    };

which assumes that the object is an array.

Iโ€™m having the same issue. Removing the validation made it work, but I really need validation.