1

I am trying to figure out how to use React with Formik's Field Arrays to embed repeatable form components in my form.

I have a system working (I think). It's a combination of suggestions sourced from 10+ sources, so I'm starting to think I am off the plantation because there must be an obvious way to set this up (and I just can't figure it out).

At the moment, my current approach is generating a warning that:

A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/docs/forms.html#controlled-components

My code sandbox is here: https://codesandbox.io/s/goofy-glade-lx65p?from-embed. Code from the sandbox is pasted at the bottom of this post for those who prefer not to use this tool.

You can see that the form renders and the repeatable embedded form can be added and removed. However, the embedded form has an error that says: this.props.formik.registerField is not a function. (In 'this.props.formik.registerField(this.props.name, this)', 'this.props.formik.registerField' is undefined)

I don't know what this means. I don't use something called registerField anywhere. Am I supposed to? I have seen this post that describes setting the initial state to get around this warning, but if I do that in the requests form, then I get a blank form displayed on the main form (I don't want that, I just want the link to embed one).

Can anyone see what I'm doing wrong with the state?

Form:

import React from "react";
import { Link } from "react-router-dom";
import {
  Formik,
  Form,
  Field,
  FieldArray,
  ErrorMessage,
  withFormik
} from "formik";
// import * as Yup from "yup";
import Select from "react-select";
// import { fsDB, firebase, settings } from "../../../firebase";
import Requests from "./Requests";

import {
  Badge,
  Button,
  Col,
  ComponentClass,
  Feedback,
  FormControl,
  FormGroup,
  FormLabel,
  InputGroup,
  Table,
  Row,
  Container
} from "react-bootstrap";

const style2 = {
  paddingTop: "2em"
};

const initialValues = {
  title: "",
  Requests: [],
  createdAt: ""
};

class MainForm extends React.Component {
  state = {
    options: []
  };

  async componentDidMount() {
    // const fsDB = firebase.firestore(); // Don't worry about this line if it comes from your config.
    let options = [];
    // await fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
    // querySnapshot.forEach(function(doc) {
    //     console.log(doc.id, ' => ', doc.data());
    //     options.push({
    //         value: doc.data().title.replace(/( )/g, ''),
    //         label: doc.data().title + ' - ABS ' + doc.id
    //     });
    //     });
    // });
    this.setState({
      options
    });
  }

  handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    const payload = {
      ...formState,
      createdAt: firebase.firestore.FieldValue.serverTimestamp()
    };
    console.log("formvalues", payload);

    // fsDB
    //   .collection("project")
    //   .add(payload)
    //   .then(docRef => {
    //     console.log("docRef>>>", docRef);
    //     resetForm(initialValues);
    //   })
    //   .catch(error => {
    //     console.error("Error adding document: ", error);
    //   });
  };

  render() {
    const { options } = this.state;

    return (
      <Formik
        initialValues={initialValues}
        onSubmit={this.handleSubmit}
        render={({
          errors,
          status,
          touched,
          setFieldValue,
          setFieldTouched,
          handleSubmit,
          isSubmitting,
          dirty,
          values,
          arrayHelers
        }) => {
          return (
            <div>
              <Form>
                <Table responsive>
                  <thead>
                    <tr>
                      <th>#</th>
                      <th>Element</th>
                      <th>Insights</th>
                    </tr>
                  </thead>
                </Table>
                {/*General*/}
                <h5 className="formheading">general</h5>
                <Table responsive>
                  <tbody>
                    <tr>
                      <td>1</td>
                      <td>
                        <div className="form-group">
                          <label htmlFor="title">Title</label>
                          <Field
                            name="title"
                            type="text"
                            className={
                              "form-control" +
                              (errors.title && touched.title
                                ? " is-invalid"
                                : "")
                            }
                          />
                          <ErrorMessage
                            name="title"
                            component="div"
                            className="invalid-feedback"
                          />
                        </div>
                      </td>
                      <td className="forminsight">No insights</td>
                    </tr>
                  </tbody>
                </Table>
                {/*Method*/}
                {/* <SoPTip /> */}

                {/*Resources s*/}
                <Table responsive>
                  <tbody>
                    <tr>
                      <td>
                        <label htmlFor="Requests">Request</label>

                        <FieldArray
                          name="Requests"
                          render={arrayHelpers => (
                            <React.Fragment>
                              {values.Requests.map((funding, i) => (
                                <Requests
                                  setFieldValue={setFieldValue}
                                  arrayHelpers={arrayHelpers}
                                  values={values}
                                  data={funding}
                                  key={i}
                                  index={i}
                                />
                              ))}
                              <div>
                                <Button
                                  variant="link"
                                  size="sm"
                                  onClick={() =>
                                    arrayHelpers.push({
                                      title: "",
                                      description: "",
                                      source: "",
                                      disclosure: "",
                                      conflicts: ""
                                    })
                                  }
                                >
                                  Add request
                                </Button>
                              </div>
                            </React.Fragment>
                          )}
                        />
                      </td>
                      <td className="forminsight">No insights</td>
                    </tr>
                  </tbody>
                </Table>
                <div className="form-group">
                  <Button
                    variant="outline-primary"
                    type="submit"
                    id="ProjectId"
                    onClick={handleSubmit}
                    disabled={!dirty || isSubmitting}
                    onSubmit={values => console.log(values)}
                  >
                    Save
                  </Button>
                </div>
              </Form>
            </div>
          );
        }}
      />
    );
  }
}

export default MainForm;

Request repeatable form:

import React from "react";
import { Field } from "formik";
import Select from "react-select";

import { Table, Button } from "react-bootstrap";

const Currencies = [
  { value: "usd", label: "United States Dollars (USD)" },
  { value: "nzd", label: "New Zealnd Dollars (NZD)" },
  { value: "gbp", label: "Pounds Sterling (GBP)" },
  { value: "cad", label: "Canadian Dollars (CAD)" }
];

class Requests extends React.Component {
  render() {
    const {
      index,
      values,
      setFieldValue,
      setFieldTouched,
      arrayHelpers,
      remove
    } = this.props;

    return (
      <div>
        <Table responsive>
          <tbody>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequestsTitle">Title</label>

                  <Field
                    name={`Requests.${index}.title`}
                    placeholder="Add a title"
                    className="form-control"
                  />
                </div>
              </td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequestsSource">Source</label>
                  <Field
                    name={`Requests.${index}.source`}
                    component="textarea"
                    rows="10"
                    placeholder="Leave blank and skip ahead if you don't"
                    className="form-control"
                  />
                </div>
              </td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="Currency">Select your currency</label>

                  <Select
                    key={`my_unique_select_keyCurrency`}
                    name={`Requests.${index}.Currency`}
                    className={"react-select-container"}
                    classNamePrefix="react-select"
                    onChange={({ value: selectedOption }) => {
                      console.log(selectedOption);
                      setFieldValue(
                        `Requests.${index}.Currencies`,
                        selectedOption
                      );
                    }}
                    onBlur={setFieldTouched}
                    options={Currencies}
                  />
                </div>
              </td>
              <td className="forminsight">No insights</td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequestsAmount">
                    What amount of funding are you seeking?
                  </label>
                  <Field
                    name={`Requests.${index}.amount`}
                    type="text"
                    placeholder="Enter a number without a currency symbol"
                    className={"form-control"}
                  />
                </div>
              </td>
              <td className="forminsight">No insights</td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequestsDisclosure">Disclosure</label>
                  <Field
                    name={`Requests.${index}.disclosure`}
                    component="textarea"
                    rows="10"
                    placeholder="Consider."
                    className="form-control"
                  />
                </div>
              </td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequetsConflicts">Conflicts</label>
                  <Field
                    name={`Requests.${index}.conflicts`}
                    component="textarea"
                    rows="10"
                    placeholder="Take a look at the checklist. "
                    className="form-control"
                  />
                </div>
              </td>
              <td className="forminsight">No insights</td>
            </tr>
            <tr>
              <td>
                <div className="form-group">
                  <label htmlFor="RequestsBudget">
                    Do you have a budget for how this funding will be deployed?
                  </label>
                  <Field
                    name={`Requests.${index}.budget`}
                    component="textarea"
                    rows="10"
                    placeholder="Leave blank if you don't."
                    className="form-control"
                  />
                </div>
              </td>
              <td className="forminsight">No insights</td>
            </tr>
          </tbody>
        </Table>
        <Button
          variant="outline-secondary"
          size="sm"
          onClick={() => arrayHelpers.remove(index)}
        >
          Remove this request
        </Button>
      </div>
    );
  }
}

export default Requests;
Mel
  • 2,481
  • 26
  • 113
  • 273
  • Really, you're supposed to post your code here, not on a third-party site. Also, the code you did post is not much use since all I see is a white screen when I load it and no errors - and frankly I don't have time to go working out why. WRT to the warning, that usually means that some form values are undefined and then change to a value or vice versa. – see sharper Oct 21 '19 at 22:28
  • 1
    @seesharper - the code sandbox is helpful because you can see the code run rather than just read it static on the screen. Clearly not your cup of tea, but it is an approach i have adopted because of feedback received from other questions (and answers to them) asked on this forum. Thanks anyway for your attention. – Mel Oct 21 '19 at 22:30
  • I get it. But the site guidelines say otherwise, rightly or wrongly. You can get your question closed as a result. – see sharper Oct 21 '19 at 22:37
  • Hey @Mel did you manage to figure this issue out? I've encountered it this week and this is the closest question I've found so far – MichaelM Jul 20 '21 at 19:59
  • Hi @MickelsonMichael - I think I gave up trying shortly after asking this question. I found another form builder that seemed to work better for me. Good luck. – Mel Jul 29 '21 at 00:44

1 Answers1

0

The reason that I find for that warning is that you have Requests in your initialValues but you are using fundingRequests in your form.
when I changed

<Field
  name={`fundingRequests.${index}.title`}
  placeholder="Add a title"
  className="form-control"
 />

to

 <Field
   name={`Requests.${index}.title`}
   placeholder="Add a title"
   className="form-control"
 />

there would be no warning anymore. let me know if this does not solve the problem

Shahab Emami
  • 324
  • 1
  • 7
  • Hi @Shahab, thanks for taking a look. No - it doesn't solve the problem. I though I had cleaned all of the references to fundingRequests up (this is a change I made to ask this question on this forum, to try and reduce the number of confusing things to read). They all reference Requests not and the problem persists. – Mel Oct 22 '19 at 00:01
  • I can't reproduce the error that you are mentioning. Can you please tell me when and how that happens? – Shahab Emami Oct 22 '19 at 07:27
  • it's the warning in the console – Mel Oct 22 '19 at 10:57