web-dev-qa-db-ja.com

反応選択オプション配列に、firebaseコレクションからのキーと値のペアを入力します

フォームにreact-selectを使用し、オプションがfirebaseコレクションに格納されているオプションアプリでオプション配列を使用しようとしています。

キー値のペアで定義するオプションの配列を使用してフォームにconstを定義すると、これはすべて正常に機能しますが、その配列をFirebase(Cloud Firestore)に格納されているコレクションで置き換える方法を理解するのに苦労しています。

私のフォームでは、私は現在持っています:

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" }
];

この配列を、データベースコレクションのドキュメントタイトルのマップに置き換えます。

データベース内のドキュメント名にキーがあり、各ドキュメントには「タイトル」という単一のフィールドがあります。

私のフォームで選択していただきありがとうございます。

<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>

配列の使用に関するFirebaseドキュメントを読みましたが、これを実行する方法として少なくとも20の異なるパスをたどる何か(おそらく明白)がありません。

これが適切かどうかはわかりませんが、私のフォームはFormikで作成されています。

Constオプションの配列を、firebaseデータベースコレクションのキーと値のペアのマップに置き換えるにはどうすればよいですか?

私のオプションを次のように定義しようとしました:

const options = fsDB.collection("abs_for_codes")

しかし、ページは私が解読できないエラーでいっぱいです。私はこのユーザーガイドを読みましたが、インデックスに関する指示を理解していません。また、この問題について知っておく必要があるものかどうかもわかりません。

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

私も試しました:

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

しかし、それはドキュメントを理解しようとすることから推測しているだけです。

私がfirebase docsに示されている正確な定式化を試してみると、次のようになります:

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

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

次のように、判読できないエラーメッセージの全ページが表示されます。

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

別の試み:

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

これも機能しません。reactarrayの指示に似ているため、試してみました。

添付画像は、firestoreのデータ構造を示しています。

data structure

次の試み

マレーの提案を使用して、私は試しました

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>

したがって、ステップ実行すると、オプションは空の配列として開始され、ComponentDidMount関数はその状態をNewOptionsにリセットし、フォームの選択ドロップダウンに入力されます。

それはすべて私には理にかなっていますが、機能しません-空の配列を取得するだけです。

Avanthikaの提案を試してみると、フォームをレンダリングでき、適切なdbコレクションから複数のオプションを選択できますが、フォームを送信しても何も起こりません。 reactのコンソールデバッガーは、ニコリのない顔を見せます(これまでに見たことがない。以下の写真)。このフォームは、selectフィールドを削除すると問題なく送信されます。

enter image description here

次の試み

murray RとAvinthikaの以下の更新された提案をそれぞれ試すと、複数のフィールドを選択できます。ただし、フォームを送信できません。選択フィールドを削除すると、フォームが送信されます。 formikマルチフィールドフォームを送信するコツはありますか?

私の送信ボタンは:

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

                >
                  Save
                </Button>
              </div>

私のハンドル送信は:

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

コンソールは何も記録しません。

次の試み

反応chrome拡張機能を削除して再インストールしましたが、再び機能します。

添付のスクリーンショットは、フォームが検証されておらず、送信されていないことを示していますが、各フォーム値の状態がそこにあります。ショットの下部に、フォームフィールド値の1つが「s」として表示されています。

enter image description here

さらなる試み

したがって、私はこのフォームを1つのフィールド(ここで作業しようとしている選択フィールド)のみを持つフォームに分割しました。

その形式には、全体として次のような特徴があります。

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;

このフォームでは、フォーム内の研究分野を選択できます。 on submit関数は、fieldOfResearchの成功を「未定義」として記録する限り、コンソールで機能します。データベースには何も保持されません。

エラーメッセージは次のとおりです。未処理の拒否(FirebaseError):関数DocumentReference.set()が無効なデータで呼び出されました。サポートされていないフィールド値:未定義(フィールドfieldOfResearchにあります)▶

フィールド値を入力して反応値を調べようとすると、エラーメッセージに次のように表示されます。

Uncaught TypeError:未定義またはnullをオブジェクトに変換できません

5
Mel

Firestoreからリサーチフィールドを取得した後にReactコンポーネントを更新するには、optionsを次のように宣言する必要がありますReact値の変更に注意を払います。これは、クラスの状態内にオプションを保存し、後でsetState()を使用して更新することで実行できます。

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

次に、componentDidMount()関数内でFirestoreコレクションを呼び出し、_state.options_に結果を入力します。

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

これにより、Firestoreから「abs_for_codes」ドキュメントが取得され、Select要素に新しいデータが入力されるようにコンポーネントのoptionsプロパティに割り当てられます。

1
MurrayR

それで、このようなものは役に立ちますか?

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

これは、「abs_code」コレクション内のすべてのドキュメントを取得し、それらをループして、各エントリをオブジェクトとして「options」配列にプッシュします。

1
M.Lewis