Step 3
Displaying data from our API
When developing a React Application, you want to separate the components that manipulate data, application state, etc... , and components that purely display data. These "dumb" components are usually just called components and these "smart" components are sometimes referred to as "containers". We want to display our list of books, but we want to separate the data fetching from the visual components.
Let's revisit our Book component and rewrite it to take in a Book object and display it
src/components/Book.tsx
import React from 'react';
import { IBook } from '../types';
const BookComponent = ({ book }: { book: IBook }) => {
return (
<div>
<h3>Id: {book.id}</h3>
<h3>Title: {book.title}</h3>
<h3>Author: {book.author}</h3>
</div>
);
};
export default BookComponent;
Here, we are rewriting our React class component as a function. It takes in 1 argument, an object of properties. We are only concerned with our book property, so we can directly destructure it from the function parameters. Since we are using Typescript, we can also add a type on this destructured property of "IBook". By doing this, whenever we render our book in other components and we do not specify a book property or we do and the value is not of type "IBook", we will receive an error. This way, we can catch bugs early through our Typescript compiler.
We are going to render a list of books, so let's create another component to do that:
src/components/BookList.tsx
import React from 'react';
import Book from './Book';
import { IBook } from '../types';
const BooksList = ({ books }: { books: IBook[] }) => {
return (
<div>
{books.map(book => (
<div key={book.id}>
<Book book={book} />
<hr />
</div>
))}
</div>
);
};
export default BooksList;
We are doing something similar to our Book component, but instead we are mapping over our array of book objects and converting them into HTML elements. Notice that on line 11, we are specifying a "key" property. Specifying a key allows React to know which item in the list has changed, and if you don't specify one, you might see a warning in your browser console. We render our Book component like normal and also provide it with the book that it needs to render. Notice how our components do not manipulate any of our data, they only display the data. The fetching of our data is left up to our container.
src/containers/Books.tsx
import React, { Component } from 'react';
import { fetchBooks } from '../actions';
import BooksList from '../components/BookList';
export default class BooksPage extends Component {
state = {
books: [],
loading: true
};
async componentDidMount() {
try {
const books = await fetchBooks();
this.setState({ books, loading: false });
} catch (error) {
console.error('Error fetching books:', error);
this.setState({ loading: false });
}
}
render() {
const { books, loading } = this.state;
if (loading) return <div>Loading...</div>;
return (
<div>
The books page
<br />
Books:
{books.length ? <BooksList books={books} /> : <div>No Books!</div>}
</div>
);
}
}
A main functionality of React components is that they each individually keep track of their own internal state. When we create our component, initially there are no books and we start off by fetching books from our API. In React, there are life cycle methods. "componentDidMount" is a function that will run after the component has been initially rendered. When the component is initially rendered, we are in the loading state and we display "Loading..." to the user. Then, React runs our "componentDidMount" method, where we will asynchronously fetch the books and update the state once that is completed. Notice that we don't directly mutate the state object, and instead call the "setState" method. If you do not use this function, React will not re-render the component.
Finally, we will use our Books container in our App
src/App.tsx
import React, { Component } from 'react';
import './App.css';
import BooksPage from './containers/Books';
class App extends Component {
render() {
return (
<div className="App App-header">
<BooksPage />
</div>
);
}
}
export default App;
We will also make some slight changes to the CSS
src/App.css
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
.App-header {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
src/index.css
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #282c34;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
Last updated
Was this helpful?