0

How should I fix my React App which renders two logs with different results even though the code has only one console.log in the application? Yes, I removed <React.StrictMode> in my index.js file because it also trigger renders twice. In the terminal the first log is an undefined and the second one has the object with data, because of that when I use array map method, it keeps saying " BookDetail.jsx:26 Uncaught TypeError: bookData.map is not a function" .

I'm trying to fetch the detailed information about a book from the firebase database. My goal is very simple, a frontend user clicks a title of book and it takes to the detailed page. All the data stores in firestore database. The detailed page code is below, hoping somebody can help me out, thank you!

import React, {useState, useEffect} from 'react'
import {Link} from 'react-router-dom'
import firebase from '../config/fbConfig'

const BookDetails = (props) => {
    const id = props.match.params.id
    const db = firebase.firestore()
    const [books, setBooks] = useState('')
        useEffect(() => {
            db.collection("books")
            .doc(id)
            .get()
            .then(doc => doc.data())
            .then(data => setBooks(data))
            },[])
            const bookData = {books}             

    return (
        <div className="book_details">
            <Link to="/"><h2>Home</h2></Link>     
                {console.log(bookData)}
            <h1>The Summary Of the Book </h1>
                {bookData.map(book => <div key={book.id}> {book.brief} </div>)}
        </div>
    )
}
export default BookDetails
  • Functional components re-render when their parent does. Also, review this [lifecycle diagram](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/), "rendering" occurs in the render phase and can be paused, aborted, or restarted by React, meaning, react can call render as many times as is necessary to compute what needs to be committed to the DOM. Does this answer your question? [Why does useState cause the component to render twice on each update?](https://stackoverflow.com/questions/61578158/why-does-usestate-cause-the-component-to-render-twice-on-each-update) – Drew Reese Jun 25 '20 at 01:09

1 Answers1

0

Per ReactJS documentation, it's recommended to call hooks at top level of component: https://reactjs.org/docs/hooks-rules.html

So it looks like when your component mounts it calls your custom useBooks hook.

It initializes books state and then executes the useEffect. However, I think calling to the Firestore db function is an async process and because you're not waiting for the response, your function is returning undefined.

It seems like you may not need your custom hook. Set up useState and useEffect at the top level.

const BookDetails = (props) => {
    const id = props.match.params.id
    const db = firebase.firestore()
    const [books, setBooks] = useState([])

           useEffect(() => {
             const fetchBooks = () => {
                db.collection("books")
                  .doc(id)
                  .get()
                  .then(doc => doc.data())
                  .then(data => setBooks(data))
               }
             fetchBooks()
            },[])

    return (
        <div className="book_details">
            <Link to="/"><h2>Home</h2></Link>     
                {console.log(books.title)}
            <h1>The Summary Of the Book </h1>
                {books && books.map(book => <div key={book.id}> {book.brief} </div>)}
        </div>
    )
}
export default BookDetails

const data = doc.data() appears to be an async function, so you may want to chain another .then for setBooks

Hyetigran
  • 1,150
  • 3
  • 11
  • 29
  • I think you are correct in that extracting the `data` from the response returns a promise, but you may want to also return `data` in that first then-block, or `.then(doc => doc.data())`. – Drew Reese Jun 25 '20 at 02:48
  • Good catch, I was setting it to a variable but not returning it. Updated per above snippet. – Hyetigran Jun 25 '20 at 03:28
  • I removed useBooks component and useEffect is running top level now; However the problem is still remain the same. I am unable to map the data into the application. When I console.log, it logs two different results, the first one is undefined and the second one through the object. I think because the first log comes with undefined, that why map method doesn't recognize the object and through bookData.map is not a function error message. Do you have any idea what I should do? – Jampa Thinlay Tsarong Jun 25 '20 at 19:49
  • Edited the above code snippet. Added books in the return statement that way if books array is empty (falsey) the second part doesn't run. Modified the useEffect hook so that it only runs once by removing books and setBooks. Let me know if that works for you. – Hyetigran Jun 26 '20 at 04:09
  • Two questions regarding to the snippet above, function **fetchBooks** never used and after **async**, there is no **await** statement, is that correct? Based on the above snippet it doesn't return the value of books, even after setBooks(data). Any idea? Thanks! – Jampa Thinlay Tsarong Jun 26 '20 at 19:49
  • Updated code. We declared fetchBooks but forgot to call it. Also, removed async since we're chaining .then() instead. Usually you go with one or the other. – Hyetigran Jun 26 '20 at 22:29
  • It produces TypeError message: books.map is not a function. Somehow it still doesn't read the books ... :( – Jampa Thinlay Tsarong Jun 29 '20 at 19:40
  • That usually means that the thing before the dot is not an array. Console log `data` in your fetchBooks function to make sure that you're storing the right property as books. `.then(data => {console.log(data) setBooks(data))` – Hyetigran Jun 29 '20 at 19:59
  • Please ignore the error message as I mentioned above: "TypeError: books.map is not a function". You are right map is Array Method. However, based on the code we have, it renders twice the first one produces "undefined" and the second one throws the objects with book title, because of the first render is "undefined" that why the map method throws books.map is not a function error message. I'm stuck in this can't get out :((( – Jampa Thinlay Tsarong Jun 29 '20 at 21:07