Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-firestore-hooks to fetch database records from cloud firestore

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?

like image 957
Mel Avatar asked Nov 12 '19 07:11

Mel


People also ask

How do I get firestore data in react?

Consuming Data from Firestore First, import db from the config file along with useState and useEffect to create state , and fire the request to fetch data. Create a piece of state to store your data. Create an async function to fetch data from Firestore and call it inside useEffect , as shown below.

How do I get data from Firebase database in react?

First, you need to create your application in the Firebase console. Then, head over to the Database menu and scroll a down a bit into the Choose Real-Time Database section. Set the security rules to start in test mode. This makes your database insecure, but it's okay for the purpose of this tutorial.

How do I access my firestore database?

Navigate to the Cloud Firestore section of the Firebase console. You'll be prompted to select an existing Firebase project. Follow the database creation workflow.


1 Answers

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'; 😏

like image 82
Bonjour123 Avatar answered Nov 11 '22 01:11

Bonjour123