DJ2
DJ2

Reputation: 1741

React - checkbox values are undefined despite having a default value

I have a bit of an issue that’s causing size and maxArrayElements on all checkboxes selected after the first checkbox to be undefined if the input boxes are untouched. They are all set to default to 1 if not touched.

So in the sandbox, pick a dataschema, selectorField, check one box, just choose the lengthType, and hit submit. The object will come back (in the console) with the default values for size and arrayElements of 1.

Now if I check another box, just choose the lengthType, and hit submit, the size and arrayElements values come back undefined if not touched. They should default to 1. I do have a placeholder value set to 1, so it might be misleading. Specifically, the value prop on the inputs value={this.state.size[lastCheckedFieldName] || 1} handle this by setting the default value to 1 if not changed. But for some reason this isn't happening

This sandbox is reproducing the issue.

import React from "react";
import ReactDOM from "react-dom";
import { Checkbox, CheckboxGroup } from "react-checkbox-group";
import axios from "axios";

import "./styles.css";

const schemas = [{ name: "Phone Data", id: "1" }];

const data = {
  data: {
    id: "2147483601",
    name: "Phone Data",
    fields: [
      {
        name: "Callee #"
      },
      {
        name: "Caller #"
      },
      {
        name: "Duration"
      }
    ]
  }
};

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

    this.state = {
      schemas: [],
      fields: [],
      size: {},
      lengthType: {},
      maxArrayElements: {},
      fieldNames: [],
      submitDisabled: true
    };

    this.onDataSchemaChange = this.onDataSchemaChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleCancel = this.handleCancel.bind(this);
  }

  componentDidMount() {
    axios({
      method: "get",
      url: `/list/of/schemas`
    })
      .then(response => {
        console.log(response);
        this.setState({ schemas: schemas });
      })
      .catch(error => console.log(error.response));
  }

  onDataSchemaChange = event => {
    const schema = this.state.schemas.find(
      schema => schema.name === event.target.value
    );
    if (schema) {
      axios({
        method: "get",
        url: ``
      })
        .then(response => {
          console.log(response);
          this.setState({
            fields: data.data.fields,
            selectedId: data.data.id
          });
          console.log(this.state.selectedId);
          console.log(this.state.fields);
        })
        .catch(error => console.log(error.response));
    }
  };

  onSizeChange = e => {
    e.persist();
    const { fieldNames } = this.state;
    const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
    this.setState(
      prevState => {
        return {
          size: {
            ...prevState.size,
            [lastCheckedFieldName]: e.target.value
          }
        };
      },
      () => {
        console.log(this.state.size);
      }
    );
    console.log([e.target.name]);
  };

  onChangeMaxArrayElements = e => {
    e.persist();
    const { fieldNames } = this.state;
    const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
    this.setState(
      prevState => {
        return {
          maxArrayElements: {
            ...prevState.maxArrayElements,
            [lastCheckedFieldName]: e.target.value
          }
        };
      },
      () => {
        console.log(this.state.maxArrayElements);
      }
    );
    console.log([e.target.name]);
  };

  handleSelectorFieldChange = event => {
    this.setState({ selectorField: event.target.value });
  };

  handleCancel = event => {
    event.target.reset();
  };

  fieldNamesChanged = newFieldNames => {
    this.setState({
      submitDisabled: !newFieldNames.length,
      fieldNames: newFieldNames,
      size: {
        [newFieldNames]: 1,
        ...this.state.size
      },
      maxArrayElements: {
        [newFieldNames]: 1,
        ...this.state.maxArrayElements
      }
    });
    console.log(this.state.size);
    console.log(this.state.maxArrayElements);
  };

  updateSelectorField = e => {
    this.setState({ selectorField: e.target.value });
  };

  updateLengthType = e => {
    e.persist();
    const { fieldNames } = this.state;
    const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
    console.log("e", e);
    this.setState(prevState => {
      const lengthType = { ...prevState.lengthType };
      lengthType[lastCheckedFieldName] = e.target.value;
      return {
        lengthType
      };
    });
  };

  handleSubmit = event => {
    event.preventDefault();
    const fields = this.state.fieldNames.map(fieldName => ({
      name: fieldName,
      lengthType: this.state.lengthType[fieldName],
      size: this.state.size[fieldName],
      maxArrayElements: this.state.maxArrayElements[fieldName]
    }));
    console.log(fields);
  };

  render() {
    const { schemas, fields, fieldNames, selectorField } = this.state;
    const lastCheckedFieldName = fieldNames[fieldNames.length - 1];

    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <fieldset>
            <legend>
              <h2>QuerySchema Information</h2>
            </legend>
            <div className="info-boxes">
              <div>
                <label> Pick the dataschema to describe your data file:</label>{" "}
                <select
                  name="schemaName"
                  value={this.state.value}
                  onChange={this.onDataSchemaChange}
                >
                  <option value="">Choose Dataschema ...</option>
                  {schemas &&
                    schemas.length > 0 &&
                    schemas.map(schema => {
                      return <option value={schema.name}>{schema.name}</option>;
                    })}
                </select>
              </div>
            </div>
          </fieldset>
          <fieldset>
            <legend>
              <h2> DataSchema Fields </h2>
            </legend>
            <div className="selectorField-div">
              <div>
                <label>Selector Field:</label>{" "}
                <select
                  value={this.state.selectorField}
                  onChange={this.updateSelectorField}
                  required
                >
                  <option value="">Pick Selector Field...</option>
                  {fields &&
                    fields.map(field => {
                      return <option value={field.name}>{field.name}</option>;
                    })}
                </select>
              </div>
              {selectorField && (
                <fieldset>
                  <div>
                    <legend>Choose field names</legend>
                    <CheckboxGroup
                      checkboxDepth={5}
                      name="fieldNames"
                      value={this.state.fieldNames}
                      onChange={this.fieldNamesChanged}
                      required
                    >
                      {fields &&
                        fields.map(field => {
                          return (
                            <li>
                              <Checkbox value={field.name} />
                              {field.name}
                            </li>
                          );
                        })}
                    </CheckboxGroup>
                  </div>{" "}
                </fieldset>
              )}
            </div>
            <div className="input-boxes">
              {lastCheckedFieldName && (
                <div>
                  <label>Length Type:</label>
                  <select
                    value={this.state.lengthType[lastCheckedFieldName] || ""}
                    onChange={this.updateLengthType}
                    required
                  >
                    <option value="">Select Length Type...</option>
                    <option value="fixed">Fixed</option>
                    <option value="variable">Variable</option>
                  </select>
                </div>
              )}
              {lastCheckedFieldName && (
                <div>
                  <label>Size:</label>
                  <input
                    value={this.state.size[lastCheckedFieldName] || 1}
                    onChange={this.onSizeChange}
                    type="number"
                    name="size"
                    min="1"
                    placeholder="1"
                    required
                  />
                </div>
              )}
              {lastCheckedFieldName && (
                <div>
                  <label>MaxArray Elements:</label>
                  <input
                    value={
                      this.state.maxArrayElements[lastCheckedFieldName] || 1
                    }
                    onChange={this.onChangeMaxArrayElements}
                    type="number"
                    name="maxArrayElements"
                    placeholder="1"
                    min="1"
                    max="100"
                    required
                  />
                </div>
              )}
            </div>
          </fieldset>

          <div className="btn-group">
            <span className="input-group-btn">
              <button
                className="btnSubmit"
                handleSubmit={this.handleSubmit}
                type="submit"
                disabled={this.state.submitDisabled}
              >
                Submit{" "}
              </button>
              <button
                className="btnReset"
                handleCancel={this.handleCancel}
                type="reset"
                onClick={() => {
                  alert("Clearing current field values.");
                }}
              >
                Reset
              </button>
            </span>
          </div>
        </form>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 0

Views: 1094

Answers (1)

Diceros
Diceros

Reputation: 83

I have checked the output of the state at the end of your handleSubmit function, and it looks like this:

lengthType: {
  "Callee #": "fixed",
  "Caller #": "variable"
},
maxArrayElements: {
  "Callee #,Caller #": 1,
  "Callee #": 1
}
fieldNames: [
 0: "Callee #"
 1: "Caller #"
]

The issue here is that, the keys in both lengthType and maxArrayElements are named incorrectly. You are setting these key-value pairs onChange of the CheckboxGroup. The root of the issue is in the parameters passed to fieldNamesChanged function. CheckboxGroup always pass the Array of checked checkboxes, not just the new one. I would suggest to get only the last record from newFieldNames and add it to state.

fieldNamesChanged = newFieldNames => {
    this.setState({
      submitDisabled: !newFieldNames.length,
      fieldNames: newFieldNames,
      size: {
        [newFieldNames[newFieldNames.length - 1]]: 1,
        ...this.state.size
      },
      maxArrayElements: {
        [newFieldNames[newFieldNames.length - 1]]: 1,
        ...this.state.maxArrayElements
      }
    });
  };

Upvotes: 1

Related Questions