1

TLDR - You should not implement/call your reusable component within your data fetching request. The correct way to do so is having a container component that is responsible for fetching the data, storing it in state, and passing it down to a presentational component that is responsible for rendering the data as UI. (example below)


Initially what I was trying to do (I assumed components are converted to HTML and can be created as string - BAD PRACTICE):

fetch('http://localhost:8000/api/Books')
        .then(response => {
            if (!response.ok) {
                throw Error('Network request failed.')
            }
            return response;
        })
        .then(data => data.json())
        .then(data => {
            let output = ''
            for (let book of data) {
                output += (
                    <Book id={book._id}
                          title={book.title}
                          author={book.author}
                          genre={book.genre}
                          read={book.read} />
                    );
            }
            console.log('parsed json', data);
            document.getElementById('database').innerHTML = output;
        }, (ex) => {
            this.setState({
                requestError : true
            });
            console.log('parsing failed', ex)
        })

My question was:
How do i make this work? how do I Implement my Book component inside the GET request to render a reusable Book for every object in the database?

My solution
having <BooksContainer /> as my container component books data is stored in state and is iterated using .map to render each book object as a <Book /> component - our presentational function.

    //BooksContainer.js - responsible for data fetching and storing
class BooksContainer extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            books: [];
        };
    }
    componentDidMount () {
        fetch('http://localhost:8000/api/Books')
            .then(response => {
                if (!response.ok) {
                    throw Error('Network request failed.')
                }
                return response;
            })
            .then(data => data.json())
            .then(data => {
                this.setState({
                    books: data
                });
                console.log('parsed json', data);            
            }, (ex) => {
                this.setState({
                    requestError : true
                });
                console.log('parsing failed', ex)
            })
    }   
    render () {
        return (
            <div className="books-container">
                <ul className="books-list">
                    {this.state.books.map(book => <Book {...book} />}
                </ul>
            </div>
        )
    }

    //Book.js - responsible for presenting each book's data
    const Book = (props) => (
        <li>
            <span className="title">{this.props.title}</span>
            <span className="author">Author: {this.props.author</span>
            <span className="genre">Genre: {this.props.genre}</span>
            <span className="read">Read: {this.props.read ? "Yes" : "No"}</span>
        </li>
    )

2 Answers 2

4

You should store the result of the api inside the state and render the <Book /> inside the render function.

Btw it would be better to separate your components:

  1. The container that do all the logic (data fetching, ...).
  2. The presentational component that renders the ui.

You can even go further by using redux for handling a global state, and redux-saga for handling the side effects (api calls)

EDIT

Here is a small example.

The presentational component:

const BookListing = ({ books }) => (
  <ul>
    {books.map(book => <li key={book.id}>{book.title}</li>)}
  </ul> 
);

The container component:

class Books extends Component {
  constructor(props) {
    super(props);
    this.state = {books: []};
  }
  componentDidMount() {
    fetch('http://localhost:8000/api/Books')
      .then(data => data.json())
      .then((data) => { this.setState({ books: data }) }); 
  }
  render() {
    return <BookListing books={this.state.books} />;
  }
}
Sign up to request clarification or add additional context in comments.

10 Comments

Could you provide an example/tutorial for that? I'm not sure that OP will get it.
I have thought about this possibility and tried to do this myself, I couldn't really figure it out right away. since state stores a single information each time it can't store every book separately, or can it? Well then, as my friend here, Valentine said, I'd be happy to receive any kind of guidance with this task.
AFTER EDIT I tried to do that and got: "books.map is not a function" error.
maybe the data you are receiving from the api is not an array, or is empty ?
So I did some checking, and something really weird is happening. I console.log this.state.books, when i dont refer to the data in the presentational component (BookListing) at all and the console shows the Object with all the books inside. then while still console.logging my Books.state.books I change my code and refer {books} in any way within my BookListing component (trying to map it for example) the console shows an empty object. Whats going on here?? EDIT I tried to only console.log books in BookListing and the console shows the full object again.
|
0

1) Create a container component where you can do your AJAX request and then save the result in your local state, say books in the render method.

2) Pass the this.state.books into your <Book /> component where you can iterate over the array.

3) (Optional but recommended). You can create another component like <BookDetail /> to render individual book item

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.