I have a react component that would show all the books in the database. However when i click the button the first time it will not list out the books but if i click the button again it will list out all the books, how do i make it list out all the books by only clicking once?
import PropTypes from "prop-types";
import {getBook} from "../actions/bookActions";
import {connect} from "react-redux";
import {Container} from "@material-ui/core";
import {Table} from "react-bootstrap";
import {Button} from "react-bootstrap";
const bookList = []
class BookListing extends Component {
constructor(){
super();
this.state= {
id: "",
};
this.onSubmit = this.onSubmit.bind(this);
}
async onSubmit(e){
this.setState({[e.target.name]: e.target.value});
e.preventDefault();
const data = await this.props.getBook();
try {
console.log(data);
bookList.splice(0, bookList.length)
data.forEach(book => {
console.log(book)
bookList.push(book)
})
}catch (exception){
console.log("no books")
}
}
render() {
const { errors } = this.state;
return (
<Container>
<form onSubmit={this.onSubmit}>
<Button variant="dark" type="submit">Show</Button>{' '}
<p>Double click the button</p>
</form>
<h2 color={"green"}>{"\n"}Books found {"\n"}</h2>
<br/>
<br/>
<Table striped bordered hover variant="dark">
<thead>
<tr>
<th>Book Id</th>
<th>Title</th>
<th>Author</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{bookList.map((book => <tr>
<td>{book.id}</td>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.category}</td>
</tr>))}
</tbody>
</Table>
</Container>
)
}
}
BookListing.propTypes = {
createProject: PropTypes.func.isRequired
};
export default connect(null, {getBook})(BookListing);
Page after pressing the button once: No books are showing but the console indicates that it is there.
Page after pressing the button second time: This time the books are listed.
CodePudding user response:
Since you need to react to changes to bookList
, it needs to be part of the state.
this.state = {id:'', bookList: [] }
In onSubmit
replace the bookList
with the books from data
.
// slice without arguments clones an array
this.setState({bookList: data.slice()});
When updating state arrays in React, you should use concat
, slice
, filter
, map
and concat
rather than push
, splice
, []
etc. The former return a new array. The latter modify the existing state array, which may be ignored by react. See this article for more details.
The slice in above code, may not really necessary. (Just for demo.)
Finally, while rendering the list of <tr>
s you need to specify an unique key for each <tr>
. You can use book.id
as the key.
<tr key={book.id}>
CodePudding user response:
After the button click React component doesn't know that something has changed, because firstly you change the state with setState and then process bookList. So when bookList is changed component doesn't re-rendered, so only on the next click component will get update bookList from previous click.
Of course you can use forceUpdate after updating bookList update, but this is terrible solution. As @NiceBooks said, put bookList into the state or you can split into two components: one will be responsible for getting bookList and then just pass it to dummy component that knows only how to render bookList. Like // here is the logic to get books // here show books, handle click
And maybe instead use smth like: const updatedBookList = [...bookList, ...data]
CodePudding user response:
Booklist is a normal javascript variable in your component. so when you update that variable. react will not identify it because it is not rerendering. so to re-render your component you need to change the value in the state using setState(). currently when you click the button second time it will change the state value initially "this.setState({[e.target.name]: e.target.value});" because of this code. (I found that this set state is not using anywhere in the code also e.target.name could be undefined)
try this code.
import PropTypes from "prop-types";
import {getBook} from "../actions/bookActions";
import {connect} from "react-redux";
import {Container} from "@material-ui/core";
import {Table} from "react-bootstrap";
import {Button} from "react-bootstrap";
class BookListing extends Component {
constructor(){
super();
this.state= {
id: "",
bookList:[]
};
this.onSubmit = this.onSubmit.bind(this);
}
async onSubmit(e){
e.preventDefault();
const data = await this.props.getBook();
this.setState({bookList:data})
}
render() {
const { errors, bookList } = this.state;
return (
<Container>
<form onSubmit={this.onSubmit)}>
<Button variant="dark" type="submit">Show</Button>{' '}
<p>Double click the button</p>
</form>
<h2 color={"green"}>{"\n"}Books found {"\n"}</h2>
<br/>
<br/>
<Table striped bordered hover variant="dark">
<thead>
<tr>
<th>Book Id</th>
<th>Title</th>
<th>Author</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{bookList && bookList.length && bookList.map((book => <tr>
<td>{book.id}</td>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.category}</td>
</tr>))}
</tbody>
</Table>
</Container>
)
}
}
BookListing.propTypes = {
createProject: PropTypes.func.isRequired
};
export default connect(null, {getBook})(BookListing);