Creating BookList.jsx Component
Next, write a React child component code named "BookList.jsx" to display book contents with fetched data and query.
Layout Structure
Copy // Layout Design of BookList Component
BookList Component
│
├── Props
│ ├── books (array) - Collection of book objects
│ └── query (string) - Current search query
│
└── Render Structure
└── <div> (mt-4)
├── <h3> (text-center) - "Book Search Results for '{query}'"
│
└── Conditional Rendering
├── IF books.length > 0
│ └── <div> (row)
│ └── books.map() - Iterates through each book
│ └── <div> (col-md-4 mb-4) - For each book
│ └── <div> (card h-100)
│ └── <div> (card-body)
│ ├── <h5> (card-title) - Book title
│ │
│ ├── <p> (card-text) - Authors
│ │ ├── <strong>Authors:</strong>
│ │ └── Conditional: authors.join(', ') OR 'Unknown'
│ │
│ ├── <p> (card-text) - Description
│ │ ├── <strong>Description:</strong>
│ │ └── Conditional: truncated description OR 'No Description Available'
│ │
│ └── <p> (card-text) - Publisher
│ ├── <strong>Publisher:</strong>
│ └── Conditional: publisher OR 'Unknown'
│
└── ELSE
└── <p> (text-center) - "No books found for '{query}'"
Copy // BookList.jsx
// Import necessary libraries
import React from 'react';
import PropTypes from 'prop-types';
const BookList = ({ books, query }) => {
return (
<div className="mt-4">
{/* Heading to show the current search query */}
<h3 className="text-center">Book Search Results for "{query}"</h3>
{/* Check if books are available */}
{books.length > 0 ? (
<div className="row">
{books.map((book) => (
<div key={book.id} className="col-md-4 mb-4">
<div className="card h-100">
<div className="card-body">
{/* Display book title */}
<h5 className="card-title">{book.volumeInfo.title || 'No Title Available'}</h5>
{/* Display book authors */}
<p className="card-text">
<strong>Authors:</strong>{' '}
{book.volumeInfo.authors ? book.volumeInfo.authors.join(', ') : 'Unknown'}
</p>
{/* Display book description */}
<p className="card-text">
<strong>Description:</strong>{' '}
{book.volumeInfo.description
? book.volumeInfo.description.substring(0, 100) + '...'
: ('No Description Available')}
</p>
{/* Display book publisher */}
<p className="card-text">
<strong>Publisher:</strong>{' '}
{book.volumeInfo.publisher || 'Unknown'}
</p>
</div>
</div>
</div>
))}
</div>
) : (
<p className="text-center">No books found for "{query}"</p>
)}
</div>
);
};
// Define PropTypes for the component
BookList.propTypes = {
books: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
volumeInfo: PropTypes.shape({
title: PropTypes.string,
authors: PropTypes.arrayOf(PropTypes.string),
description: PropTypes.string,
publisher: PropTypes.string,
}).isRequired,
})
).isRequired,
query: PropTypes.string.isRequired,
};
export default BookList;
Explanation of BookList.jsx
Code Structure
The BookList
component is designed to display a list of books fetched from the API along with the query used. Here's the step-by-step explanation:
1. Importing Libraries
Copy import React from 'react';
import PropTypes from 'prop-types';
React
: Core library for creating React components.
PropTypes
: Ensures the props passed to the component are valid and well-defined.
Bootstrap : Provides styling for the layout and components.
2. Component Declaration
Copy const BookList = ({ books, query }) => {
Props :
books
: An array of book objects fetched from the API.
query
: The current search term entered by the user.
3. Display Book Query Search Results
Copy <h3 className="text-center">Results for "{query}"</h3>
Purpose :
Displays the current search query to inform the user of the context.
4. Conditional Rendering
Copy {books.length > 0 ? (
// Render book cards if books are available
) : (
<p className="text-center">No books found for "{query}"</p>
)}
If books exist :
Render a list of book cards dynamically using map
.
If no books are found :
Show a user-friendly message.
Copy <div className="row">
{books.map((book) => (
<div key={book.id} className="col-md-4 mb-4">
<div className="card h-100">
<div className="card-body">
<h5 className="card-title">{book.volumeInfo.title || 'No Title Available'}</h5>
<p className="card-text"><strong>Authors:</strong> {book.volumeInfo.authors ? book.volumeInfo.authors.join(', ') : 'Unknown'}</p>
<p className="card-text"><strong>Description:</strong> {book.volumeInfo.description ? book.volumeInfo.description.substring(0, 100) + '...' : 'No Description Available'}</p>
<p className="card-text"><strong>Publisher:</strong> {book.volumeInfo.publisher || 'Unknown'}</p>
</div>
</div>
</div>
))}
</div>
map
Function :
Iterates over the books
array to dynamically generate a Bootstrap card for each book.
Book Details :
Title : Displays the book's title or a fallback message.
Authors : Joins the list of authors or displays "Unknown"
.
Description : Shows the first 100 characters of the description with a fallback.
Publisher : Displays the publisher or "Unknown"
.
Search Result Style: modify
6. Prop Validation
Copy BookList.propTypes = {
books: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
volumeInfo: PropTypes.shape({
title: PropTypes.string,
authors: PropTypes.arrayOf(PropTypes.string),
description: PropTypes.string,
publisher: PropTypes.string,
}).isRequired,
})
).isRequired,
query: PropTypes.string.isRequired,
};
Purpose :
Validates the structure and types of books
and query
props to prevent runtime errors.
Details :
books
is an array of objects with a defined structure.
query
is a string and must be provided.
7. Exporting the Component
Copy export default BookList;
Purpose :
Makes the BookList
component reusable in other parts of the app.
Key Takeaways for Students :
Dynamic Rendering :
Use the map
function to generate a list of items dynamically.
Handle missing or undefined data gracefully with fallback values.
Conditional Logic :
Use conditional rendering to display appropriate content when data is unavailable.
Prop Validation :
Use PropTypes
to ensure data passed to components meets the expected format.
Styling with Bootstrap :
Quickly style components using Bootstrap classes for layout and spacing.
This component is ready to integrate into the parent AppBookShelf.jsx
. Let me know if you need further explanation or want to proceed with the next child component!
Challenge :
Add "Read More" button to get the full description about the book when clicked by creating a new component "ReadMore.jsx"
Implement a react child component named "ReadMore.jsx" to show the full description of the book selected when clicked "Read More" button and when clicked "Close" button, go back to the previous state.
Copy // Code Snippet in BookList.jsx
{/* Display book description */}
<p className="card-text">
<strong>Description:</strong>{' '}
{book.volumeInfo.description ? (
<ReadMore text={book.volumeInfo.description} />
) : (
'No Description Available'
)}
</p>
Copy // Components/ReadMore.jsx
// Import necessary libraries
import React, { useState } from 'react'
import PropTypes from 'prop-types'
const ReadMore = ({ text }) => {
const [isExpanded, setIsExpanded] = useState(false)
// Toggle the expanded state
const toggleExpanded = () => {
setIsExpanded(!isExpanded)
}
return (
<div>
{isExpanded ? (
<>
<p>{text}</p>
<button className="btn btn-secondary btn-sm" onClick={toggleExpanded}>
Close
</button>
</>
) : (
<>
<p>{text.length > 100 ? `${text.substring(0, 100)}...` : text}</p>
{text.length > 100 && (
<button className="btn btn-primary btn-sm" onClick={toggleExpanded}>
Read More
</button>
)}
</>
)}
</div>
)
}
// Define PropTypes for the component
ReadMore.propTypes = {
text: PropTypes.string.isRequired,
}
export default ReadMore