6

I'm trying to figure out how to use react-firebase-hooks in my react app so that I can simplify calls on my database.

My previous version (solved with help on this question) of this attempt used this class component with a componentDidMount function (it worked):

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

I'm now trying to learn how to use hooks to get the data from the database using react-firebase-hooks. My current attempt is:

import { useDocumentOnce } from 'react-firebase-hooks/firestore';

I have also tried import { useDocument } from 'react-firebase-hooks/firestore';

const [snapshot, loading, error] = useDocumentOnce(
  firebase.firestore().collection('abs_for_codes'),
  options.push({
    value: doc.data().title.replace(/( )/g, ''),
    label: doc.data().title + ' - ABS ' + doc.id
  }),
);

This generates an error that says: 'useDocumentOnce' is not defined

I tried (that's also incorrect):

const [snapshot, loading, error] = useDocumentOnce(
  firebase.firestore().collection('abs_for_codes'),
  {snapshot.push({
    value: doc.data().title.replace(/( )/g, ''),
    label: doc.data().title + ' - ABS ' + doc.id,
  })},
);

How do I get a collection from firebase? I'm trying to populate a select menu with the options read from a collection in firebase called abs_for_codes.

I think the point of useState is that I don't need to declare a state anymore, I can just call I have added my select attempt below:

<Select 
            className="reactSelect"
            name="field"
            placeholder="Select at least one"
            value={valuesSnapshot.selectedOption}
            options={snapshot}
            onChange={handleMultiChangeSnapshot}
            isMulti
            ref={register}
          />

For reference, I have 2 other select menus in my form. The consts I use to set up the options for those are manually defined, but the process to establish their values is below:

const GeneralTest = props => {
  const { register, handleSubmit, setValue, errors, reset } = useForm();
  const { action } = useStateMachine(updateAction);
  const onSubit = data => {
    action(data);
    props.history.push("./ProposalMethod");
  };

  const [valuesStudyType, setStudyType] = useState({
    selectedOptionStudyType: []
  });

  const [valuesFundingBody, setFundingBody] = useState({
    selectedOptionFundingBody: []
  });


  const handleMultiChangeStudyType = selectedOption => {
    setValue("studyType", selectedOption);
    setStudyType({ selectedOption });
  };

  const handleMultiChangeFundingBody = selectedOption => {
    setValue("fundingBody", selectedOption);
    setFundingBody({ selectedOption });
  };

  useEffect(() => {
    register({ name: "studyType" });
    register({name: "fundingBody"});
  }, []);

How do I add the snapshot from the database query?

I have tried making similar handleMultiChange const and useEffect register statements for the snapshot, like so:

  const [snapshot, loading, error] = useDocumentOnce(
    firebase.firestore().collection('abs_for_codes'),
    snapshot.push({
      value: snapshot.data().title.replace(/( )/g, ''),
      label: snapshot.data().title + ' - ABS ' + snapshot.id
    }),
  );

  const [valuesField, setField ] = useState({
    selectedOptionField: []
  });

  const handleMultiChangeField = selectedOption => {
    setValue("field", selectedOption);
    setField({ selectedOption });
  };

but it doesn't work. The error message says:

ReferenceError: Cannot access 'snapshot' before initialization

I can't find an example of how to populate the select menu with the data from the database.

NEXT ATTEMPT

useEffect(
    () => {
      const unsubscribe = firebase
        .firestore()
        .collection('abs_for_codes')
        .onSnapshot(
          snapshot => {
            const fields = []
            snapshot.forEach(doc => {
              fields.push({
                value: fields.data().title.replace(/( )/g, ''),
                label: fields.data().title + ' - ABS ' + fields.id
              })
            })
            setLoading(false)
            setFields(fields)
          },
          err => {
            setError(err)
          }
        )
      return () => unsubscribe()
    })

This doesn't work either - it produces an error message that says:

TypeError: fields.data is not a function

NEXT ATTEMPT

Recognising that i need to search the collection rather than a call on document, but still not sure whether useCollectionData is more appropriate than useCollectionOnce (I can't make sense of the documentation about what useCollectionData offers), I have now tried:

const [value, loading, error] = useCollectionOnce(
  firebase.firestore().collection('abs_for_codes'),
  {getOptions({
    firebase.firestore.getOptions:
    value: doc.data().title.replace(/( )/g, ''),
    label: doc.data().title + ' - ABS ' + doc.id,
  })},
);

This is also incorrect. The error message points to the getOptions line and says: Parsing error: Unexpected token, expected ","

In my collection, i have a number of documents. Each has 2 attributes, a number and a text string. My options are to format the number and the text string so they appear together as well as an acronym i have inserted as text (as I was able to do using componentDidMount).

NEXT ATTEMPT

I next tried this:

const fields = firebase.firestore.collection("abs_for_codes").get().then(function(querySnapshot) {
  querySnapshot.forEach(function(doc) {
    console.log(doc.id, ' => ', doc.data());
    fields.push({
        value: doc.data().title.replace(/( )/g, ''),
        label: doc.data().title + ' - ABS ' + doc.id
    });
    });
});

The error message says: TypeError: _firebase__WEBPACK_IMPORTED_MODULE_5__.firebase.firestore.collection is not a function

NEXT ATTEPMT

const searchFieldOfResearchesOptions = (searchKey, resolver) => {
    // for more info
    // https://stackoverflow.com/questions/38618953/how-to-do-a-simple-search-in-string-in-firebase-database
    // https://firebase.google.com/docs/database/rest/retrieve-data#range-queries
    fsDB
      .collection("abs_for_codes")
      .orderBy("title")
      // search by key
      .startAt(searchKey)
      .endAt(searchKey + "\uf8ff")
      .onSnapshot(({ docs }) => {
        // map data to react-select
        resolver(
          docs.map(doc => {
            const { title } = doc.data();

            return {
              // value: doc.id,
              // label: title
              value: title.data().title.replace(/( )/g, ''),
              label: title.data().title + ' - ABS ' + title.id
            };
          })
        );
      }, setFieldOfResearchesError);
  };

This attempt actually works to retrieve data from the database (hooray) - except I can't get the text label I want to render. Each document in the collection has 2 fields. The first is a title and the second is an id number, my last step is to make a label that has inserted text (ie ABS - ) and then the id number and the title together.

I have added the commented code to show what works to extract each document's title, but the extra bit I tried to make the label the way I want it doesn't present an error, it just doesn't work - I still only get the document title in the list.

Does anyone know how to generate a select menu set of options from a cloud firestore collection using hooks?

Soni Sol
  • 2,367
  • 3
  • 12
  • 23
Mel
  • 2,481
  • 26
  • 113
  • 273
  • ill make a new bounty to try to get help with this problem tomorrow - i'm still trying to figure out how to use firebase – Mel Nov 21 '19 at 07:35

3 Answers3

3

import { useDocument } from 'react-firebase-hooks/firestore';

Why ? You are using useDocumentOnce, of course you need to import this function, and no useDocument, which you don't use.

Last error:You are using a const before even it has been initialized, hence the

ReferenceError: Cannot access 'snapshot' before initialization

snapshot is going to be initialized by useDocumentOnce, you cannot use it (snapshot) as an argument passed to the function going to initialize it.

Plus, I had a look on react-firebase-hooks, here is the documentation of useDocumentOnce: enter image description here

Use this example, and adapt it to use the document you want to use.

import { useDocument } from 'react-firebase-hooks/firestore';

const FirestoreDocument = () => {
  const [value, loading, error] = useDocument(
    firebase.firestore().doc('hooks/nBShXiRGFAhuiPfBaGpt'),
    {
      snapshotListenOptions: { includeMetadataChanges: true },
    }
  );
  return (
    <div>
      <p>
        {error && <strong>Error: {JSON.stringify(error)}</strong>}
        {loading && <span>Document: Loading...</span>}
        {value && <span>Document: {JSON.stringify(value.data())}</span>}
      </p>
    </div>
  );
};

You can either use useDocument as in the example, but you can also choose to use useDocumentOnce. But in this case, change the import accordingly (to import { useDocumentOnce } from 'react-firebase-hooks/firestore';

Bonjour123
  • 1,421
  • 12
  • 13
  • Thank you for your time. I had read the documentation for this tool many times and have seen the extracts you have pasted here and tried to work with them many times. None of this is useful. The documents do not contain an import statement in the useDocumentOnce example - so I tried both (as I mentioned above) to see if that were part of the problem. I am trying to figure out how to use the example I have adapted (or any other approach that works) to get records from the database as I was able to do using componentDidMount, prior to hooks. – Mel Nov 15 '19 at 00:33
  • Do you know how to initialise the const in a way that works with this tool? – Mel Nov 15 '19 at 00:36
0

There are more than one question in place here. See if this helps.

This generates an error that says: 'useDocumentOnce' is not defined

import { useDocumentOnce } from 'react-firebase-hooks/firestore';

If you're importing it just like the DOCs says you should, and you're getting this error, I would say there's something wrong with your dev environment. Try using a different code editor, or even a different PC. If you are importing it correctly like you said, there's nothing wrong in your code and you shouldn't be getting this error. This is not a bug on your code.


See the docs for useCollection and useDocumentOnce (they're very similiar):

enter image description here

enter image description here

And now see what you're doing:

const [snapshot, loading, error] = useDocumentOnce(
  firebase.firestore().collection('abs_for_codes'),
  {snapshot.push({
    value: doc.data().title.replace(/( )/g, ''),
    label: doc.data().title + ' - ABS ' + doc.id,
  })},
);

You're not sending the correct parameters for the useDocumentOnce function. It need a document reference and an options object. You're sending a collection reference and a bunch of other code to the options parameter.

Your code should be looking like this:

const [snapshot, loading, error] = useDocumentOnce(
  firebase.firestore().collection('abs_for_codes').doc('someDocID')
);

OR

const [snapshot, loading, error] = useDocumentOnce(
  firebase.firestore().collection('abs_for_codes').where('someProperty','==','someValue')
);

Note that I didn't include the options parameter, since it's optional. Just send it if you need some specific option.


What a component should look like using the useDocumentOnce hook.

import { useDocumentOnce } from 'react-firebase-hooks/firestore';

const FirestoreDocument = () => {
  const [snapshot, loading, error] = useDocumentOnce(
    firebase.firestore().collection('someCollection').doc('someDocID'),
    {
      snapshotListenOptions: { includeMetadataChanges: true },
    }
  );
  return (
    <div>
      <p>
        {error && <strong>Error: {JSON.stringify(error)}</strong>}
        {loading && <span>Document: Loading...</span>}
        {snapshot && (
          <span>
            Document: {JSON.stringify(snapshot.data()}
          </span>
        )}
      </p>
    </div>
  );
};
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • Hi @cbdev420 - I tried both useDocument and useDocumentOnce (as I said). you just made me realise that I should be trying to figure out how to use useCollection. Not sure if I should be trying to use useCollection or useCollectionData. my collection is called abs_for_codes. They each have a label and value and I want to add extra to the collection to put the name and the id together in the display. Can you make sense of the instructions for doing that, so that the function returns the list of each item in the collection? – Mel Nov 18 '19 at 08:58
  • From [react-firebase-hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore) **"React Firebase Hooks provides convenience listeners for Collections and Documents stored with Cloud Firestore."**. They are listeners, you'll not be able to add anything to the collection using it. – cbdeveloper Nov 18 '19 at 09:02
  • 1
    I recommend you get rid of the `react-firebase-hooks` for now and take a deeper dive into the [firebase and firestore docs](https://cloud.google.com/firestore/docs/manage-data/add-data). Read all of the guides. Use the `firestore` API directly, instead of using this other library. Then, further down the road, when you have a better grip of firebase and firestore, you might move back to this `react-firebase-hooks` if you want. – cbdeveloper Nov 18 '19 at 09:04
  • I had it working without hooks. Now I'm trying to figure out how to use it with hooks. – Mel Nov 18 '19 at 10:51
  • I'm not trying to add anything to it. I'm trying to pull data out of it, and then format it to add the label and the id to a string to present it - as I was able to do using componentDidMount, prior to adapting to hooks. – Mel Nov 18 '19 at 10:53
  • You should create a test project (a new one) and build a [CodeSandbox](https://codesandbox.io/) with basic functionality so we can take a deeper look into it. **Do not share your API keys for your current project.** Create a new one for testing purposes only and you can delete it afterwards. – cbdeveloper Nov 18 '19 at 11:02
0

I'm sure you've already done this, but just in case... have you installed the react-firebase-hooks dependency in your project using the terminal command npm install --save react-firebase-hooks? If you haven't installed the dependency, that could explain the message useDocumentOnce is not defined. Here's a link to the installation document. I have gotten that message before when I've forgotten to install packages or dependencies which is why your error message made me think of that.

blindgren
  • 21
  • 1