0

I have a select dropdown that when selected renders a checkbox group using <FieldArray> from formik

  <FieldArray
    name="fields"
    render={arrayHelpers => (
      <div>
        {fields.map(field => (
          <div key={field.name}>
            <label>
              <input
                name="fields"
                type="checkbox"
                value={field.name}
                onChange={e => {
                  if (e.target.checked) arrayHelpers.push(field.name);
                  else {
                    const idx = fields.indexOf(field.name);
                    arrayHelpers.remove(idx);
                  }
                }}
              />{" "}
              {field.name}
            </label>
          </div>
        ))}
      </div>
    )}
  />

So in the onChange method I need for each checkbox that is selected to render a class component that has additional input fields that are tied to the field name. For example, the size and length options need to chosen for each checkbox that is selected.

        class FieldInputs extends React.Component {
          constructor(props) {
            super(props);

            this.state = {
              lengthType: "",
              size: [],
            };

            this.lengthTypeChange = this.lengthTypeChange.bind(this);
            this.onSizeChange = this.onSizeChange.bind(this);
          }

      lengthTypeChange = lengthType => {
        //handle change method for lengthType
        this.setState({ lengthType });
        console.log("LengthType selected: ", lengthType);
      };

  onSizeChange = e => {
    this.setState({ [e.target.name]: e.target.value });
    console.log([e.target.value]);
  };
          render() {
            return (
              <div>
                <h2>
                  {" "}
                  These are the input fields for each field name checkbox selected.{" "}
                </h2>
            <div>
              <Select
                id="color"
                options={lengthTypeOptions}
                isMulti={false}
                value={lengthType}
                onChange={this.lengthTypeChange}
                onBlur={this.handleBlur}
                placeholder={"Select a lengthType..."}
              />
            </div>
            <div>
              <label>Size:</label>
              <input
                value={this.state.size}
                onChange={this.onSizeChange}
                type="number"
                name="size"
                min="1"
                placeholder="1"
                required
              />
            </div>
              </div>
            );
          }
        }

For instance each field.name is rendered to a checkbox and should have a select dropdown for lengthType and input for size like so:

{
    field.name: {
      size: 1,
      lengthType: "Fixed"
    }
}

So I have the component designed, just need to render it to each checkbox that is selected. How can I pass the FieldInput component to the checkboxes based on if they are toggled or not?

Inside the <Myselect> component, you can find the <FieldArray> component, using the onChange method

onChange={e => {
  if (e.target.checked) {
    arrayHelpers.push(field.name);

When's it's checked it also needs to render <FieldInputs>

Here's a link to a sandbox with the classes/components described above

2
  • I think you have a syntax error in your <FieldArray /> onChange. Should be if (e.target.checked) { ... } else { ... } (brackets after your condition). Commented Jan 25, 2019 at 22:07
  • This doesn't really change the functionality of the conditional Commented Jan 28, 2019 at 15:30

1 Answer 1

1
+50

I made many changes this is the full code

import "./helper.css";
import { MoreResources, DisplayFormikState } from "./helper";

import React from "react";
import { render } from "react-dom";
import { Formik, FieldArray } from "formik";
import * as Yup from "yup";
import axios from "axios";
import Select from "react-select";

var MockAdapter = require("axios-mock-adapter");
var mock = new MockAdapter(axios);

mock.onGet("/dataschemas").reply(200, {
  data: [
    {
      id: "2147483602",
      selfUri: "/dataschemas/2147483602",
      name: "Phone Data"
    }
  ]
});

mock.onGet("/dataschemas/2147483602").reply(200, {
  data: {
    id: "2147483602",
    selfUri: "/dataschemas/2147483602",
    type: "DataSchema",
    name: "Phone Record",
    fields: [
      {
        name: "action"
      },
      {
        name: "callee"
      },
      {
        name: "caller"
      },
      {
        name: "duration"
      },
      {
        name: "message"
      },
      {
        name: "time_stamp"
      }
    ]
  }
});

const lengthTypeOptions = [
  { value: "fixed", label: "Fixed" },
  { value: "variable", label: "Variable" }
];

class FieldInputs extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      lengthType: "",
      size: []
    };

    this.lengthTypeChange = this.lengthTypeChange.bind(this);
    this.onSizeChange = this.onSizeChange.bind(this);
  }

  lengthTypeChange = lengthType => {
    //handle change method for lengthType
    this.setState({ lengthType }, () => {
      this.props.update(this.props.name, this.state);
    });
    //console.log("LengthType selected: ", lengthType);
  };

  onSizeChange = e => {
    this.setState({ [e.target.name]: e.target.value }, () => {
      this.props.update(this.props.name, this.state);
    });
    //console.log([e.target.value]);
  };

  render() {
    const { lengthType } = this.state;
    return (
      <div>
        <h2>
          {" "}
          These are the input fields for each field name checkbox selected.{" "}
        </h2>
        <div>
          <Select
            id="color"
            options={lengthTypeOptions}
            isMulti={false}
            value={lengthType}
            onChange={this.lengthTypeChange}
            onBlur={this.handleBlur}
            placeholder={"Select a lengthType..."}
          />
        </div>
        <div>
          <label>Size:</label>
          <input
            value={this.state.size}
            onChange={this.onSizeChange}
            type="number"
            name="size"
            min="1"
            placeholder="1"
            required
          />
        </div>
      </div>
    );
  }
}

const App = () => (
  <div className="app">
    <h1>Formik Demo</h1>

    <Formik
      initialValues={{
        querySchemaName: "",
        schemas: [],
        fields: [],
        selectorField: "",
        lengthType: "",
        size: []
      }}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 500);
      }}
      validationSchema={Yup.object().shape({
        querySchemaName: Yup.string()
          .required("QuerySchema name is required!")
          .min(3, "Please enter a longer name")
          .max(50, "Please ener a shorter name")
      })}
    >
      {props => {
        const {
          values,
          touched,
          errors,
          dirty,
          isSubmitting,
          handleChange,
          handleBlur,
          handleSubmit,
          handleReset,
          setFieldValue,
          setTouchedValue
        } = props;
        return (
          <form onSubmit={handleSubmit}>
            <label htmlFor="querySchemaName" style={{ display: "block" }}>
              QuerySchema Name:
            </label>
            <input
              id="querySchemaName"
              placeholder="Example -- QuerySchema1"
              type="text"
              value={values.querySchemaName}
              onChange={handleChange}
              onBlur={handleBlur}
              className={
                errors.querySchemaName && touched.querySchemaName
                  ? "text-input error"
                  : "text-input"
              }
            />
            {errors.querySchemaName && touched.emaquerySchemaNameil && (
              <div className="input-feedback">{errors.querySchemaName}</div>
            )}
            <MySelect
              value={values.schemas}
              onChange={setFieldValue}
              options={values.schemas}
              onBlur={setTouchedValue}
              error={errors.topics}
              touched={touched.topics}
            />

            <button
              type="button"
              className="outline"
              onClick={handleReset}
              disabled={!dirty || isSubmitting}
            >
              Reset
            </button>
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>

            <DisplayFormikState {...props} />
          </form>
        );
      }}
    </Formik>

    <MoreResources />
  </div>
);

class MySelect extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      schemas: [],
      fields: [],
      selectorField: "",
      checked: []
    };

    this.handleChange = this.handleChange.bind(this);
    this.updateSelectorField = this.updateSelectorField.bind(this);
  }

  componentDidMount() {
    axios.get("/dataschemas").then(response => {
      this.setState({
        schemas: response.data.data
      });
      //console.log(this.state.schemas);
    });
  }

  handleChange = value => {
    // this is going to call setFieldValue and manually update values.dataSchemas
    this.props.onChange("schemas", value);
    const schema = this.state.schemas.find(
      schema => schema.name === value.name
    );
    if (schema) {
      axios.get("/dataschemas/2147483602").then(response => {
        this.setState({
          fields: response.data.data.fields
        });
        //console.log("fields are: " + this.state.fields);
      });
    }
  };

  updateSelectorField = value => {
    this.props.onChange("selectorField", value);
  };

  update = (name, value) => {
    this.setState(prev => {
      var arr = prev.checked;
      var de = null;
      for (var j = 0; j < arr.length; j++) {
        if (arr[j].name === name) {
          de = j;
        }
      }
      arr[de] = Object.assign(arr[de], value);
      console.log(arr);
      return { checked: arr };
    });
  };

  handleBlur = () => {
    // this is going to call setFieldTouched and manually update touched.dataSchemas
    this.props.onBlur("schemas", true);
  };

  checker = field => {
    var d = -1;
    for (let j = 0; j < this.state.checked.length; j++) {
      if (this.state.checked[j].name === field.name) {
        d = j;
      }
    }

    if (d >= 0) {
      return (
        <FieldInputs
          key={field.name + 1}
          name={field.name}
          update={this.update}
        />
      );
    } else {
      return null;
    }
  };

  change = e =>{
    var arr = this.state.checked;
    var de = -1;
    for (var j = 0; j < arr.length; j++) {
      if (arr[j].name === e) {
        de = j;
      }
    }
    if(de >= 0){
      delete arr[de];
    }else{
      var arr = arr.concat([
        {
          name: e,
          size: 1,
          lengthType: "Fixed"
        }
      ])
    }
    var nar = [];
    for(let i=0; i<arr.length; i++){
      if(typeof arr[i] !== "undefined"){
        nar.push(arr[i])
      }
    }
    this.setState({checked: nar});
  }

  render() {
    const schemas = this.state.schemas;
    const fields = this.state.fields;
    return (
      <div style={{ margin: "1rem 0" }}>
        <label htmlFor="color">
          DataSchemas -- triggers the handle change api call - (select 1){" "}
        </label>
        <Select
          id="color"
          options={schemas}
          isMulti={false}
          value={schemas.find(({ name }) => name === this.state.name)}
          getOptionLabel={({ name }) => name}
          onChange={this.handleChange}
          onBlur={this.handleBlur}
          placeholder={"Pick a DataSchema..."}
        />
        <label htmlFor="color">Selector Field - (select 1) </label>
        <Select
          id="color"
          options={fields}
          isMulti={false}
          value={fields.find(({ name }) => name === this.state.name)}
          getOptionLabel={({ name }) => name}
          onChange={this.updateSelectorField}
          placeholder={"Select a Selector Field..."}
        />
        {!!this.props.error && this.props.touched && (
          <div style={{ color: "red", marginTop: ".5rem" }}>
            {this.props.error}
          </div>
        )}
        <div>
          <FieldArray
            name="fields"
            render={arrayHelpers => (
              <div>
                {fields.map(field => (
                  <React.Fragment>
                    <div key={field.name}>
                      <label>
                        <input
                          name="fields"
                          type="checkbox"
                          value={field.name}
                          onChange={(e) => {
                            this.change(field.name)
                            if (e.target.checked) {
                              arrayHelpers.push(field.name);
                            } else {
                              const idx = fields.indexOf(field.name);
                              arrayHelpers.remove(idx);
                            }
                            }}
                        />{" "}
                        {field.name}
                      </label>
                    </div>
                    {this.checker(field)}
                  </React.Fragment>
                ))}
              </div>
            )}
          />
        </div>
      </div>
    );
  }
}

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

here is the codesandbox

Sign up to request clarification or add additional context in comments.

6 Comments

This is a viable solution, the only thing is the size and lengthType need to be tied to the field.name like this : { field.name: { size: 1, lengthType: "Fixed" } }
@DJ2 isn't the field.name supposed to be the name (value) of the checkbox? you can't change it to an object but I have a solution wait a minute
@DJ2 now this.state.checked contain the following objects {name: field.name, size: theSize, lengthType: theLength}
@DJ2 there is a weird error I will debug it tomorrow
I'll see if I can dig into it too and find out, seems like an error is thrown when field.name is selected then de-selected.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.