6

I am trying to use an options array in my react app, that uses react-select for the form and where the options are stored in a firebase collection.

This all works fine when I define a const in the form with an array of options that I define with key value pairs, but I'm struggling to figure out how to replace that array with the collection stored in Firebase (Cloud Firestore).

In my form, I currently have:

const options = [
  { value: "neurosciences", label: "Neurosciences - ABS 1109" },
  { value: "oncologyCarcinogenesis", label: "Oncology and Carcinogenesis  - ABS 1112" },
  { value: "opticalPhysics", label: "Optical Physics  - ABS 0205" },
  { value: "fisheriesSciences", label: "Fisheries Sciences - ABS 0704" },
  { value: "genetics", label: "Genetics - ABS 0604" },
  { value: "urbanRegionalPlanning", label: "Urban and Regional Planning - ABS 1205" }
];

I want to replace this array, with a map over the document titles in the database collection.

The document name in my database has the key and each document has a single field called 'title'.

Thank in my form select I have:

<div className="form-group">
                            <label htmlFor="fieldOfResearch">
                            Select your field(s) of research
                            </label>

                            <Select
                            key={`my_unique_select_key__${fieldOfResearch}`}
                            name="fieldOfResearch"
                            isMulti
                            className={
                                "react-select-container" +
                                (errors.fieldOfResearch && touched.fieldOfResearch ? " is-invalid" : "")
                            }
                            classNamePrefix="react-select"
                            value={this.state.selectedValue1}
                            onChange={e => {
                                handleChange1(e);
                                this.handleSelectChange1(e);
                            }}
                            onBlur={setFieldTouched}
                            options={options}
                            />
                            {errors.fieldOfResearch && touched.fieldOfResearch && 
                            <ErrorMessage
                            name="fieldOfResearch"
                            component="div"
                            className="invalid-feedback d-block"
                            />}
                            </div>

I have read the firebase documents on using arrays, but I am missing something (probably obvious) that has led me down at least 20 different paths for how to do this.

I'm not sure if this is relevant, but my forms are built with Formik.

How do I replace the const options array with a map over key value pairs from the firebase database collection?

I have tried to define my options constant as:

const options = fsDB.collection("abs_for_codes")

but the page fills up with errors that I can't decipher. I have read this user guide, but don't understand the directions relating to indexes and I'm not even clear on whether they're what I need to know for this problem.

https://firebase.google.com/docs/firestore/query-data/queries

I have also tried:

const options = fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
    querySnapshot.forEach(function (doc))
}

but that's just guessing from trying to make sense of the documentation.

When I try the exact formulation shown in the firebase docs, as:

const options = fsDB.collection("abs_for_codes");

options.get().then(function (querySnapshot) {
    querySnapshot.forEach(function (doc) {
        console.log(doc.id, ' => ', doc.data());
    });
});

I get a full page of indecipherable error messages, as follows:

TypeError: options.reduce is not a function
Select.buildMenuOptions
node_modules/react-select/dist/react-select.esm.js:4123
  4120 |   };
  4121 | };
  4122 | 
> 4123 | return options.reduce(function (acc, item, itemIndex) {
       | ^  4124 |   if (item.options) {
  4125 |     // TODO needs a tidier implementation
  4126 |     if (!_this3.hasGroups) _this3.hasGroups = true;
View compiled
new Select
node_modules/react-select/dist/react-select.esm.js:3593
  3590 | 
  3591 | var _selectValue = cleanValue(value);
  3592 | 
> 3593 | var _menuOptions = _this.buildMenuOptions(_props, _selectValue);
       | ^  3594 | 
  3595 | _this.state.menuOptions = _menuOptions;
  3596 | _this.state.selectValue = _selectValue;
View compiled
constructClassInstance
node_modules/react-dom/cjs/react-dom.development.js:11787
  11784 |     new ctor(props, context); // eslint-disable-line no-new
  11785 |   }
  11786 | }
> 11787 | var instance = new ctor(props, context);
        | ^  11788 | var state = workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null;
  11789 | adoptClassInstance(workInProgress, instance);
  11790 | {
View compiled
updateClassComponent
node_modules/react-dom/cjs/react-dom.development.js:15265
  15262 |   } // In the initial pass we might need to construct the instance.
  15263 | 
  15264 | 
> 15265 |   constructClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
        | ^  15266 |   mountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
  15267 |   shouldUpdate = true;
  15268 | } else if (current$$1 === null) {
View compiled
beginWork
node_modules/react-dom/cjs/react-dom.development.js:16265
  16262 | 
  16263 |     var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
  16264 | 
> 16265 |     return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
        | ^  16266 |   }
  16267 | 
  16268 | case HostRoot:
View compiled
performUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js:20285
  20282 |   startProfilerTimer(workInProgress);
  20283 | }
  20284 | 
> 20285 | next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
        | ^  20286 | workInProgress.memoizedProps = workInProgress.pendingProps;
  20287 | 
  20288 | if (workInProgress.mode & ProfileMode) {
View compiled
workLoop
node_modules/react-dom/cjs/react-dom.development.js:20326
  20323 | if (!isYieldy) {
  20324 |   // Flush work without yielding
  20325 |   while (nextUnitOfWork !== null) {
> 20326 |     nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        | ^  20327 |   }
  20328 | } else {
  20329 |   // Flush asynchronous work until there's a higher priority event
View compiled
HTMLUnknownElement.callCallback
node_modules/react-dom/cjs/react-dom.development.js:147
  144 |     window.event = windowEvent;
  145 |   }
  146 | 
> 147 |   func.apply(context, funcArgs);
      | ^  148 |   didError = false;
  149 | } // Create a global error event handler. We use this to capture the value
  150 | // that was thrown. It's possible that this error handler will fire more
View compiled
invokeGuardedCallbackDev
node_modules/react-dom/cjs/react-dom.development.js:196
  193 | // errors, it will trigger our global error handler.
  194 | 
  195 | evt.initEvent(evtType, false, false);
> 196 | fakeNode.dispatchEvent(evt);
      | ^  197 | 
  198 | if (windowEventDescriptor) {
  199 |   Object.defineProperty(window, 'event', windowEventDescriptor);
View compiled
invokeGuardedCallback
node_modules/react-dom/cjs/react-dom.development.js:250
  247 | function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
  248 |   hasError = false;
  249 |   caughtError = null;
> 250 |   invokeGuardedCallbackImpl$1.apply(reporter, arguments);
      | ^  251 | }
  252 | /**
  253 |  * Same as invokeGuardedCallback, but instead of returning an error, it stores
View compiled
replayUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js:19509
  19506 | 
  19507 | isReplayingFailedUnitOfWork = true;
  19508 | originalReplayError = thrownValue;
> 19509 | invokeGuardedCallback(null, workLoop, null, isYieldy);
        | ^  19510 | isReplayingFailedUnitOfWork = false;
  19511 | originalReplayError = null;
  19512 | 
View compiled
renderRoot
node_modules/react-dom/cjs/react-dom.development.js:20439
  20436 | if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
  20437 |   if (mayReplay) {
  20438 |     var failedUnitOfWork = nextUnitOfWork;
> 20439 |     replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
        | ^  20440 |   }
  20441 | } // TODO: we already know this isn't true in some cases.
  20442 | // At least this shows a nicer error message until we figure out the cause.
View compiled
performWorkOnRoot
node_modules/react-dom/cjs/react-dom.development.js:21363
  21360 |   cancelTimeout(timeoutHandle);
  21361 | }
  21362 | 
> 21363 | renderRoot(root, isYieldy);
        | ^  21364 | finishedWork = root.finishedWork;
  21365 | 
  21366 | if (finishedWork !== null) {
View compiled

Another attempt:

const options = abs_for_codes.map((title) => {
    <option key={title}
    value={id} />
}

This doesn't work either - I tried it because it looks similar to the react arrays instructions.

The attached image shows the data structure in firestore.

data structure

NEXT ATTEMPT

Using Murray's suggestion, I have tried

import Select from "react-select";
import { fsDB, firebase, settings } from "../../../firebase";

let options = [];

const initialValues = {
  fieldOfResearch: null,

}


class ProjectForm extends React.Component {
  state = {
    selectedValue1: options,
}

handleSelectChange1 = selectedValue1 => {
    this.setState({ selectedValue1 });
  };


componentDidMount() {
    fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
        let newOptions = [];
        querySnapshot.forEach(function (doc) {
            console.log(doc.id, ' => ', doc.data());
            newOptions.push({
              value: doc.data().title.replace(/( )/g, ''),
              label: doc.data().title + ' - ABS ' + doc.id
            });
        });
        this.setState({options: newOptions});
    });
}


handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    console.log("SUCCESS!! :-)\n\n", formState);
    fsDB
      .collection("project")
      .add(formState)
      .then(docRef => {
        console.log("docRef>>>", docRef);
        this.setState({ selectedValue1: null });
        this.setState({ selectedValue2: null });
        this.setState({ selectedValue3: null });
        this.setState({ selectedValue4: null });
        this.setState({ selectedValue5: null });
        this.setState({ selectedValue6: null });

        resetForm(initialValues);
      })
      .catch(error => {
        console.error("Error adding document: ", error);
      });
  };


onSubmit={this.handleSubmit}
        render={({ errors, status, touched, setFieldTouched, handleSubmit, values }) => {
          let fieldOfResearch;
          const handleChange1 = optionsObject => {
            fieldOfResearch = optionsObject;
            return (values.fieldOfResearch = optionsObject.value);
          };

<div className="form-group">
                                <label htmlFor="fieldOfResearch">
                                Select your field(s) of research
                                </label>

                                <Select
                                key=
{`my_unique_select_key__${fieldOfResearch}`}
                                name="fieldOfResearch"
                                isMulti
                                className={
                                    "react-select-container" +
                                    (errors.fieldOfResearch && touched.fieldOfResearch ? " is-invalid" : "")
                                }
                                classNamePrefix="react-select"
                                value={this.state.selectedValue1}
                                onChange={e => {
                                    handleChange1(e);
                                    this.handleSelectChange1(e);
                                }}
                                onBlur={setFieldTouched}
                                options={options}
                                />
                                {errors.fieldOfResearch && touched.fieldOfResearch && 
                                <ErrorMessage
                                name="fieldOfResearch"
                                component="div"
                                className="invalid-feedback d-block"
                                />}
                                </div>

So, stepping that through, options starts as an empty array, the ComponentDidMount function resets its state to NewOptions and that gets fed into the form select drop down.

That all makes sense to me, but it doesn't work - I just get an empty array.

When I try Avanthika's suggestion, i can render the form and multiple options can be selected from the right db collection, but nothing happens when I submit the form. The console debugger in react shows an unsmiling face (I've never seen that before. Pic below). This form submits fine when I remove the select field.

enter image description here

next attempt

when i try each of Murray R and Avinthika's updated suggestions below I can choose multiple fields. BUT i cannot submit the form. The form submits if i remove the select field. Is there a trick to submitting formik multi field forms?

My submit button is:

<div className="form-group">
                <Button
                  variant="outline-primary"
                  type="submit"
                  style={style3}
                  id="ProjectId"
                  onClick={handleSubmit}
                  disabled={!dirty || isSubmitting}

                >
                  Save
                </Button>
              </div>

My handle submit has:

handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    console.log("SUCCESS!! :-)\n\n", formState);
    fsDB
      .collection("project")
      .add({
          ...(formState),
          createdAt: firebase.firestore.FieldValue.serverTimestamp()
      })
      .then(docRef => {
        console.log("docRef>>>", docRef);
        this.setState({ selectedValue1: null, selectedValue2: null, selectedValue3: null, selectedValue4: null, selectedValue5: null, selectedValue6: null });

        // this.setState({ selectedValue1: null });
        // this.setState({ selectedValue2: null });
        // this.setState({ selectedValue3: null });
        // this.setState({ selectedValue4: null });
        // this.setState({ selectedValue5: null });
        // this.setState({ selectedValue6: null });

        resetForm(initialValues);
      })
      .catch(error => {
        console.error("Error adding document: ", error);
      });
  };

The console doesn't log anything.

next attempt

I removed and reinstalled the react chrome extension and that's working again.

The attached screen shot shows the form isn't validating and isn't submitting, but the state of each of the form values is in there - you can see the bottom of the shot shows one of the form field values as 's'.

enter image description here

further attempt

So - I split this form out into a form that only has one field- the select field that I have been trying to work on here.

That form, in its entirety, has:

import React from 'react';

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

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

const initialValues = {
    fieldOfResearch: null,
}


class ProjectForm extends React.Component {
    state = {
      options: [],  
      selectedValue1: [],
    }

    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
            });
          }


  handleSelectChange1 = selectedValue1 => {
    this.setState({ selectedValue1 });
  };

  handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    console.log("SUCCESS!! :-)\n\n", formState);
    fsDB
      .collection("project")
      .add({
          ...(formState),
          createdAt: firebase.firestore.FieldValue.serverTimestamp()
      })
      .then(docRef => {
        console.log("docRef>>>", docRef);
        this.setState({ selectedValue1: null});


        resetForm(initialValues);
      })
      .catch(error => {
        console.error("Error adding document: ", error);
      });
  };

  render() {
    const { options } = this.state;  
    return (
      <Formik
        initialValues={initialValues}
        validationSchema={Yup.object().shape({
          //   fieldOfResearch: Yup.array().required("What is your field of research?"),
        })}

        onSubmit={this.handleSubmit}
        render={({ errors, status, touched, setFieldTouched, handleSubmit, isSubmitting, dirty, values }) => {
          let fieldOfResearch;
          const handleChange1 = optionsObject => {
            fieldOfResearch = optionsObject;
            return (values.fieldOfResearch = optionsObject.value);
          };
          return (
            <div>
            <Form>
                <div className="form-group">

                                <label htmlFor="fieldOfResearch">
                                Select your field(s) of research
                                </label>
                                <Select
                                    key={`my_unique_select_key__${fieldOfResearch}`}
                                    name="fieldOfResearch"
                                    isMulti
                                    className={
                                        "react-select-container" +
                                        (errors.fieldOfResearch && touched.fieldOfResearch
                                        ? " is-invalid"
                                        : "")
                                    }
                                    classNamePrefix="react-select"
                                    value={this.state.selectedValue1}
                                    onChange={e => {
                                        handleChange1(e);
                                        this.handleSelectChange1(e);
                                    }}
                                    onBlur={setFieldTouched}
                                    options={options}
                                    />    

                                {errors.fieldOfResearch && touched.fieldOfResearch && 
                                <ErrorMessage
                                name="fieldOfResearch"
                                component="div"
                                className="invalid-feedback d-block"
                                />}
                                </div> 
                                <div className="form-group">
                                <Button
                                  variant="outline-primary"
                                  type="submit"
                                  id="ProjectId"
                                  onClick={handleSubmit}
                                //   disabled={!dirty || isSubmitting}

                                >
                                  Save
                                </Button>
                              </div>
                              </Form>

            </div>
        );
    }}
  />
);
}
}

export default ProjectForm;

This form allows the selection of a field of research in the form. The on submit function works in the console, to the extent that it logs success with a fieldOfResearch as 'undefined'. Nothing persists to the database.

The error message says: Unhandled Rejection (FirebaseError): Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field fieldOfResearch) ▶

When I try to enter a field value and inspect the react value, the error message says:

Uncaught TypeError: Cannot convert undefined or null to object

Mel
  • 2,481
  • 26
  • 113
  • 273
  • Not sure why someone is proposing edits to my question that change the label i use in my select menu or the class name that react select provides for the dropdown – Mel May 07 '19 at 21:26
  • How does the data that comes from your database collection look like? – Avanthika May 07 '19 at 21:42
  • I don't know what your question means. There is a collection in the db called abs_for_codes. Each document in that collection has an attribute called "title". The name of each document is a number (eg 1109) and the corresponding title is "Neurosciences" – Mel May 08 '19 at 01:06
  • ` const options = fsDB.collection("abs_for_codes"); options.get().then(function (querySnapshot) { querySnapshot.forEach(function (doc) { console.log(doc.id, ' => ', doc.data()); }); }); ` When you try this, what does it show on the console? Can you copy paste the console message? – Avanthika May 09 '19 at 16:52
  • Huge long list of errors - first one says: TypeError: options.reduce is not a function – Mel May 09 '19 at 22:21
  • @Mel put a place holder `const options = []`, the error is coming from react-select not receiving an array for options, and try to debug the result that @Avanthika asked for on a different variable – Jonathan Portorreal May 14 '19 at 00:06
  • @JonathanPortorreal - I have an empty array as a const for options now. I have no idea how to debug Avanthika's suggestion. It's starts from the premise that I would need to define firebaseData as an array (which I"m trying to avoid doing, and instead, using my database data from firestore) – Mel May 14 '19 at 00:14
  • i mean the suggestion posted in this comment thread, a few comments above `fsDB.collection("abs_for_codes").get().then(function (querySnapshot) { querySnapshot.forEach(function (doc) { console.log(doc.id, ' => ', doc.data()); }); });` the `doc.data()` should be the data type you want to build your options from – Jonathan Portorreal May 14 '19 at 00:16
  • 1
    Hi @Mel, I went an extra mile for you, I've set everything up, created a collection with docs & tried your code. I've shared the working snippet in the answer - take a look at it & let me know if it works. – Avanthika May 14 '19 at 15:04

3 Answers3

3

Another updated answer:

The error message says: Unhandled Rejection (FirebaseError): Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field fieldOfResearch

This error happened because your form values not valid. You are not maintaining proper formik state.

I just tried this and checked, form submit works great for me. You've written too much of excess code - we just need native formik methods and firebase. The change log is as follows:

  1. The onChange of react-select should use setFieldValue from Formik like this:
onChange={selectedOptions => {
   // Setting field value - name of the field and values chosen.
   setFieldValue("fieldOfResearch", selectedOptions)}
}
  1. The initial value should be an empty array. Since we have initialValues declared and the formvalues maintained via Formik, there's absolutely no need for internal state management. I.E, there's no need for this.state.selectedValue1, handleChange1 and handleSelectChange1. If you take a look at the render() of your Formik HOC, you'll notice values - This gives current value of the form after every change.

So,

value={this.state.selectedValue1}

should be changed to

value={values.fieldOfResearch}
  1. I've written the handleSubmit like this - The exact replica of your code. But I'm only extracting values from the array of selected options:
handleSubmit = (formState, { resetForm }) => {
  // Now, you're getting form state here!
  const fdb = firebase.firestore();
  const payload = {
    ...formState,
    fieldOfResearch: formState.fieldOfResearch.map(t => t.value)
  }
  console.log("formvalues", payload);
  fdb
  .collection("project")
  .add(payload)
  .then(docRef => {
    console.log("docRef>>>", docRef);
    resetForm(initialValues);
  })
  .catch(error => {
    console.error("Error adding document: ", error);
  });
}

I'm able to see the form submission & the docRef in the console. The form also gets reset to initial state.

import React from "react";
import { Formik, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import Select from "react-select";
import firebase from "./firebase";
import {
  Button,
  Container
} from "react-bootstrap";

const initialValues = {
  fieldOfResearch: []
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      options: []
    };
  }

  async componentWillMount() {
    const fdb = firebase.firestore();
    let options = [];
    await fdb
      .collection("abs_codes")
      .get()
      .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
          options.push({
            value: doc.data().title.replace(/( )/g, ""),
            label: doc.data().title
          });
        });
      });
    this.setState({
      options
    });
  }

  handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    const fdb = firebase.firestore();
    const payload = {
      ...formState,
      fieldOfResearch: formState.fieldOfResearch.map(t => t.value)
    }
    console.log("formvalues", payload);
    fdb
    .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 (
      <Container>
        <Formik
          initialValues={initialValues}
          validationSchema={Yup.object().shape({
              fieldOfResearch: Yup.array().required("What is your field of research?"),
          })}
          onSubmit={this.handleSubmit}
          render={({
            errors,
            status,
            touched,
            setFieldValue,
            setFieldTouched,
            handleSubmit,
            isSubmitting,
            dirty,
            values
          }) => {
            return (
              <div>
                <Form>
                  <div className="form-group">
                    <label htmlFor="fieldOfResearch">
                      Select your field(s) of research
                    </label>
                    <Select
                      key={`my_unique_select_keyfieldOfResearch`}
                      name="fieldOfResearch"
                      isMulti
                      className={
                        "react-select-container" +
                        (errors.fieldOfResearch && touched.fieldOfResearch
                          ? " is-invalid"
                          : "")
                      }
                      classNamePrefix="react-select"
                      value={values.fieldOfResearch}
                      onChange={selectedOptions => {
                        setFieldValue("fieldOfResearch", selectedOptions)}
                      }
                      onBlur={setFieldTouched}
                      options={options}
                    />
                    {errors.fieldOfResearch && touched.fieldOfResearch &&
                      <ErrorMessage
                         name="fieldOfResearch"
                         component="div"
                         className="invalid-feedback d-block"
                      />
                    }
                  </div>
                  <div className="form-group">
                    <Button
                      variant="outline-primary"
                      type="submit"
                      id="ProjectId"
                      onClick={handleSubmit}
                      disabled={!dirty || isSubmitting}
                    >
                      Save
                    </Button>
                  </div>
                </Form>
              </div>
            );
          }}
        />
      </Container>
    );
  }
}

export default App;

Just try copy pasting this first, and on top of this, try making your changes. I guess this should be helpful for you!


Updated Answer:

Hi Mel, I just set the whole thing in my system and tried doing it for you, although I cannot share the creds with you, I guess this should help.

  1. Javascript is not synchronous. Your componentDidMount will not wait for the data you're trying to get from firebase. It will just set the state before your query returns response.

  2. They key is to await the response. I've edited the code that way, and I'm able to see the options on my console in the render().

import React from 'react';
import firebase from "./firebase.js";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.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
        });
      });
    });
    this.setState({
      options
    });
  }

  render() {
    console.log(this.state);
    const { options } = this.state;
    return (
      <div className="form-group">
        <label htmlFor="fieldOfResearch">
          Select your field(s) of research
        </label>

        <Select
          key={`my_unique_select_key__${fieldOfResearch}`}
          name="fieldOfResearch"
          isMulti
          className={
            "react-select-container" +
            (errors.fieldOfResearch && touched.fieldOfResearch
              ? " is-invalid"
              : "")
          }
          classNamePrefix="react-select"
          value={this.state.selectedValue1}
          onChange={e => {
            handleChange1(e);
            this.handleSelectChange1(e);
          }}
          onBlur={setFieldTouched}
          options={options}
        />
      </div>
    );
  }
}

export default App;

Let me know if this works for you!

And I couldn't help but notice, why so many setStates in handleSubmit? You're forcing your component to rerender that many times. Instead you can do:

handleSubmit = (formState, { resetForm }) => {
    // Now, you're getting form state here!
    console.log("SUCCESS!! :-)\n\n", formState);
    fsDB
      .collection("project")
      .add(formState)
      .then(docRef => {
        console.log("docRef>>>", docRef);
        this.setState({ selectedValue1: null, selectedValue2: null, selectedValue3: null, selectedValue4: null, selectedValue5: null, selectedValue6: null });
        resetForm(initialValues);
      })
      .catch(error => {
        console.error("Error adding document: ", error);
      });
  };

Avanthika
  • 3,984
  • 1
  • 12
  • 19
  • Hi - the firebase table is called 'abs_codes'. It has a collection of records, each of which is saved as a number (ie: 0704) and its document has a field called title: (ie Fisheries Sciences). In my form, I want use that collection so that the documents in it are used as the options. – Mel May 07 '19 at 23:09
  • I don't want to define a const called firebaseData - and list each database entry, I just want to figure out how to use the collection as the array in the form. – Mel May 07 '19 at 23:12
  • In any event- I tried to use the idea of your suggestion, but it throws an error saying map is not a function in firebase – Mel May 07 '19 at 23:24
  • actually - I'm debugging this now. it's getting closer. still not functioning but throwing new errors. i have to go to meetings now but will keep trying with this tonight. thanks for your help – Mel May 14 '19 at 21:12
  • :) Let me know if you're facing anymore errors. Do you see the options now? – Avanthika May 15 '19 at 08:04
  • hi - no, I'm still stuck. I updated the post with the current status. This is is confusing! Thanks for your help – Mel May 15 '19 at 08:12
  • I don't understand your problem. Arent you not getting the options? Or is the form submit failing after selecting the option? – Avanthika May 15 '19 at 08:35
  • failing on select. it doesn't fail without the select field in the form. i separated out the select field so that its the only field in the form. the entire form is copied to the end of my post above. it jams up at summit - no feedback on selected options in react state field so i can't use the inspector to see if this code even works in the console and there is another challenge in getting it to persist to the db. Firebase is so complicated! – Mel May 15 '19 at 08:37
  • @Mel, I just corrected few things in your Formik, it works good now. Can you try checking it ? – Avanthika May 15 '19 at 10:05
  • @Mel, is it working for you? If yes, can you please accept my solution? – Avanthika May 15 '19 at 17:50
  • im not in your time zone. I've just seen it. I'll test it shortly. Thanks for helping – Mel May 15 '19 at 20:44
  • 1
    Thank you. I had the key expression wrong in the select form as well. This works. Thank you very much! – Mel May 15 '19 at 21:01
1

So would something like this help?

function SomeComponentName(props) {
  const [options, setOptions] = React.useState([]);

  React.useEffect(() => {
    getOptions()
  }, []}

  async function getOptions() {
    const tmpArr = [];

    try {
      // Perform get() request and loop through all docs
      await fsDB
        .collection("abs_codes")
        .get()
        .then(snapshot => {
          snapshot.forEach(doc => {
            const { title } = doc.data();
            const label = `${title} - ABS ${doc.key}`;

            tmpArr.push({ value: title, label });
          });
          setOptions(tmpArr);
        });
    } catch (err) {
      console.log("Error getting documents", err);
    }
  }

  return (
    <div className="form-group">
      <label>
        <Select
          // ...
          options={options}
          // ...
        />
      </label>
    </div>
  );
}

This will get all documents within the 'abs_code' collection, loop through them, and push each entry as an object to the 'options' array.

M.Lewis
  • 961
  • 8
  • 18
  • why is the options array empty? Is your suggestion that I include your absCodesRef in the const options, or in the form select? If the latter, what do you suggest the options array is used for? – Mel May 08 '19 at 06:19
  • The options array is simply being created by that statement, as it will be filled by the next code. When you call 'absCodeRef.get()...', the forEach loop will take each document, create the object with the label/value, and push it to the 'options' array. – M.Lewis May 08 '19 at 06:58
  • You need to call the absCodeRef.get() function when your React component mounts (or at a time before you need to render the Select), as it is then used in the Select to create each option. Does that make sense? – M.Lewis May 08 '19 at 07:01
  • Not sure how to use your suggestion. I tried it as is, but react doesn't accept it. Thanks anyway, but I don't have time to try to learn your way of thinking to figure out if this might help – Mel May 08 '19 at 07:04
  • Sorry - no I don't get what you mean. I'll keep looking for a solution – Mel May 08 '19 at 07:05
  • ok no worries. I just updated the code to show how it may look in a React component - in case that helps. – M.Lewis May 08 '19 at 07:25
  • I tried to use this, there are a bunch of errors. I stopped trying after solving the 5th one. I think it has to do with the key value pairs that the select in react-select is looking for. Thanks anyway for trying to help – Mel May 08 '19 at 07:48
1

For your React component to update after retrieving your research fields from Firestore, you'll need to declare your options in a way that React will pay attention to its value changes. You can do this by saving options within the class's state and update it later using setState():

// At the top of the class,
state = {
  options: [],
  // Any other state properties,
};

Then, within the componentDidMount() function, make your call to your Firestore collection and populate state.options with the results:

fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
    let newOptions = [];
    querySnapshot.forEach(function (doc) {
        console.log(doc.id, ' => ', doc.data());
        newOptions.push({
          value: doc.data().title.replace(/( )/g, ''),
          label: doc.data().title + ' - ABS ' + doc.id
        });
    });
    setState({options: newOptions});
});

This should retrieve your 'abs_for_codes' documents from Firestore and assign them to your component's options property in a way that will allow your Select element to be populated with the new data.

MurrayR
  • 426
  • 3
  • 6
  • Sorry, I just noticed you comments from other answers explaining your Firestore collections. Given that info, change the statements in my answer to: ``` newOptions.push({ value: doc.data().title, label: doc.data().title + ' - ABS ' + doc.id }); ``` – MurrayR May 13 '19 at 19:01
  • I don't follow this, but I tried your suggestion. I have to declare the let statement inside the function but when I do, there are at least 6 errors in the construct. I solve the first batch but don't want to keep debugging this code to see if there is something useful in it. I also don' understand the comment above about the label. It might be shorthand for something, but I'm not clever enough to figure it out. Thanks anyway for trying to help. – Mel May 13 '19 at 21:16
  • I updated my answer to reflect a class-based React component example. – MurrayR May 13 '19 at 22:53
  • Hi Murray - I tried this. I don't get any errors, but I only get an empty array - it doesn't populate with the firestore data. I've posted the current form data above. – Mel May 14 '19 at 00:21
  • Hi Mel, Remove the `let options = [];` statement at the top. You'll want to declare `options` as part of your class component's `state` so that your React form will know to 'react' to changes in its value. To do so, modify your `state = {}` assignment to include `options: []`. (I tweaked my answer to reflect your example.) When the Firestore call in `componentDidMount()` runs, it finishes by updating `state.options` with its newly retrieved/build `newOptions` array. AND, in your form, you'll need to update the `options={this.state.options}` like so that it references your retrieved options. – MurrayR May 14 '19 at 14:06
  • Hi @MurrayR - I tried this. When I set the initial state of options to ={}, I get an error that says: TypeError: options.reduce is not a function – Mel May 14 '19 at 20:59
  • When you're initializing your `state` object, it should contain `options: []`. (An empty array.) Your ` – MurrayR May 14 '19 at 21:42
  • yep - i now have it that way. the form won't submit though. If I remove the select field from the form, it does submit – Mel May 14 '19 at 21:54
  • Hi Mel, The form not submitting seems like an issue with React-Select/Formik neither of which I've used before so I don't know that I can help. As to your original question here, is the Select getting populated with your Firestore data successfully now? – MurrayR May 15 '19 at 14:18
  • Hi Murray - thanks for your help. It's solved now and submitting via Formik. Thanks very much for helping – Mel May 15 '19 at 21:01
  • That's great! I'm glad I could help. – MurrayR May 15 '19 at 21:34