Search code examples
reactjsgoogle-cloud-firestorereact-componentreact-dom

Dynamically rendering child components in react


I'm using firestore database to store my data in the collection "listings". So for each document in "listings", I need to render a <BookListing/> element in Home.js with the data from each document. From my research, there are a few other questions similar to this one out there, but they're outdated and use different react syntax. Here's my code:

function BookListing({id, ISBN, title, image, price}) {
    
    return (
        <div className="bookListing">
            <div className='bookListing_info'>
                <p className="bookListing_infoTitle">{title}</p>
                <p className="bookListing_infoISBN"><span className="bookListing_infoISBNtag">ISBN: </span>{ISBN}</p>
                <p className="bookListing_infoPrice">
                    <small>$</small>
                    {price}
                </p>
            </div>
            <img className="bookListing_img" src={image} alt=""></img>
            <button className="bookListing_addToCart">Add to Cart</button>
        </div>
    )
}

export default BookListing

function Home() {

    document.title ="Home";

    useEffect(() => {
        getDocs(collection(db, 'listings'))
        .then(queryCollection => {
            queryCollection.forEach((doc) => {
                console.log(doc.id, " => ", doc.data());
                const element = <BookListing id="456" ISBN="0101" title="sample_title" image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg" price="25"/>;
                ReactDOM.render(
                    element,
                    document.getElementById('home-contents-main')
                );
            })
        });
    }, []);



    return (
        <div className="home">
            <div className="home_container">
                <div id="home-contents-main" className="home_contents">
                </div>
            </div>
        </div>
    )
}
export default Home

Solution

  • It's best (and most common) to separate the task into two: asynchronously fetching data (in your case from firestore), and mapping that data to React components which are to be displayed on the screen.

    An example:

    function Home() {
      // A list of objects, each with `id` and `data` fields.
      const [listings, setListings] = useState([]) // [] is the initial data.
    
      // 1. Fetching the data
    
      useEffect(() => {
        getDocs(collection(db, 'listings'))
          .then(queryCollection => {
            const docs = [];
            queryCollection.forEach((doc) => {
              docs.push({
                id: doc.id,
                data: doc.data()
              });
    
              // Update the listings with the new data; this triggers a re-render
              setListings(docs);
            });
          });
      }, []);
    
      // 2. Rendering the data
    
      return (
        <div className="home">
          <div className="home_container">
            <div className="home_contents">
              {
                listings.map(listing => (
                  <BookListing
                    id={listing.id}
                    ISBN={listing.data.ISBN}
                    title={listing.data.title}
                    image={listing.data.image}
                    price={listing.data.price}
                  />
                ))
              }
            </div>
          </div>
        </div>
      );
    }
    

    Some tips:

    • Fetching data from other web servers or services can be, and typically is, done in the same manner.
    • This example could be improved a lot in terms of elegance with modern JS syntax, I was trying to keep it simple.
    • In most cases, you don't want to use ReactDOM directly (only for the entry point of your app), or mess with the DOM manually; React handles this for you!
    • If you're not familiar with the useState hook, read Using the State Hook on React's documentation. It's important!