Alex Alex - 10 days ago 5
React JSX Question

React component issues with state, setState and render

I am building a React based project for study purposes. I am stuck on making a table component, that renders itself and then sends ajax request to mbaas backend to get all book entries and fill each on a new row. Here is what I've come up so far. Please forgive the large chunk of code, but since I don't yet fully understand the interactions between methods, state and render() here is the whole class:

class BooksTable extends Component {
constructor(props) {
super(props);
this.state = {
books: []
};

this.updateState = this.updateState.bind(this);
this.initBooks = this.initBooks.bind(this);
}

componentDidMount() {
let method = `GET`;
let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}`;
let headers = {
"Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`,
"Content-Type": `application/json`
};

let request = {method, url, headers};
$.ajax(request)
.then(this.initBooks)
.catch(() => renderError(`Unable to connect. Try again later.`));
}

deleteBook(id) {
let method = `DELETE`;
let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}/${id}`;
let headers = {
"Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`,
"Content-Type": `application/json`
};

let request = {method, url, headers};
$.ajax(request)
.then(() => this.updateState(id))
.catch(() => renderError(`Unable to delete, something went wrong.`));
}

updateState(id) {
for (let entry of this.state.books.length) {
if (entry.id === id) {
// Pretty sure this will not work, but that is what I've figured out so far.
this.state.books.splice(entry);
}
}
}

initBooks(response) {
console.log(`#1:${this.state.books});
console.log(`#2:${this});
for (let entry of response) {
this.setState({
books: this.state.books.concat([{
id: entry._id,
name: entry.name,
author: entry.author,
description: entry.description,
price: Number(entry.name),
publisher: entry.publisher
}])
}, () => {
console.log(`#3${this.state.books}`);
console.log(`#4${this}`);
});
}
}

render() {
return (
<div id="content">
<h2>Books</h2>
<table id="books-list">
<tbody>
<tr>
<th>Title</th>
<th>Author</th>
<th>Description</th>
<th>Actions</th>
</tr>
{this.state.books.map(x =>
<BookRow
key={x.id}
name={x.name}
author={x.author}
description={x.description}
price={x.price}
publisher={x.publisher} />)}
</tbody>
</table>
</div>
);
}
}


Now the BookRow is not very interesting, only the onClick part is relevant. It looks like this:

<a href="#" onClick={() => this.deleteBook(this.props.id)}>{owner? `[Delete]` : ``}</a>


The Link should not be visible if the logged in user is not publisher of the book. onClick calls deleteBook(id) which is method from BookTable. On successful ajax it should remove the book from state.books (array) and render.

I am particularly confused about the initBooks method. I've added logs before the loop that populates the state and as callbacks for when the state is updated. Results from log#1 and log#3 are identical, same for logs#2#4. Also if I expand log#2 (before setState) or log#4, both of those show state = [1]. How does this make sense? Furthermore if you take a look at logs#1#3 - they print [ ]. I am probably missing some internal component interaction, but I really cant figure out what.

Thanks.

Answer

The setState doesn't immediately update the state. So in the second iteration of your for loop, you wont be getting the new state. So make your new book list first and then set it once the new list is prepared.

Try this:

initBooks(response) {
    console.log(this.state.books, "new books not set yet")
    let newBooks = []
    for (let entry of response) {
     newBooks.push({
                id: entry._id,
                name: entry.name,
                author: entry.author,
                description: entry.description,
                price: Number(entry.name),
                publisher: entry.publisher
            })
    }
    this.setState({books: [...this.state.books, newBooks]}, () => {
        console.log(this.state.books, "new books set in the state")
    })
}