React应用setState后分页子组件未重新渲染的问题求助
React搜索功能分页组件不更新问题
我正在为一个简易React应用实现搜索功能,控制台日志显示已成功获取新数据,但负责构建带分页的数据展示页面的子组件,在初始渲染后似乎再也没有被调用。
问题原因
核心问题出在Pagination组件的setPage函数中:当搜索时先清空bookList再加载新数据,此时旧的分页状态(pager)中totalPages为0,后续调用setPage(1)时,会触发page > pager.totalPages的判断(1>0),导致函数提前返回,无法生成新的分页数据和调用父组件的onChangePage更新展示内容。
另外,BookCard组件在构造函数中保存props到state,导致props更新时组件内容无法同步更新。
解决方案
1. 修复setPage函数的页码合法性判断
基于当前books的实际长度计算总页数,而非依赖旧状态中的分页数据:
setPage(page) { const books = this.props.books; if (!books || books.length === 0) { // 空数组时清空分页状态并通知父组件 this.setState({ pager: {} }); this.props.onChangePage([]); return; } // 基于当前books计算总页数 const pageSize = this.state.pager.pageSize || 8; const totalPages = Math.ceil(books.length / pageSize); if (page < 1 || page > totalPages) { return; } const pager = this.getPager(books.length, page); const pageofBooks = books.slice(pager.startIndex, pager.endIndex + 1); this.setState({ pager: pager }); this.props.onChangePage(pageofBooks); }
2. 优化componentDidUpdate逻辑
针对空数组情况单独处理,避免无效计算:
componentDidUpdate(prevProps, prevState) { if (this.props.books !== prevProps.books) { if (this.props.books && this.props.books.length) { this.setPage(this.props.initialPage); } else { this.setState({ pager: {} }); this.props.onChangePage([]); } } }
3. 修复BookCard组件的props同步问题
改为函数组件直接使用props,避免state与props不同步:
const BookCard = ({ book }) => { return ( <div className="book-card"> <div className="image__container"> <img src={book.imageUrl} /> </div> <div className="book-card__header"> <h3>{book.authorName}</h3> <h2>{book.title.length > 40 ? book.title.slice(0, 40) + '...' : book.title}</h2> </div> </div> ); };
修改后的完整代码
const propTypes = { books: React.PropTypes.array.isRequired, onChangePage: React.PropTypes.func.isRequired, initialPage: React.PropTypes.number } const defaultProps = { initialPage: 1 } class Pagination extends React.Component { constructor(props) { super(props); this.state = { pager: {} }; } componentWillMount() { if (this.props.books && this.props.books.length) { this.setPage(this.props.initialPage); } } componentDidUpdate(prevProps, prevState) { if (this.props.books !== prevProps.books) { if (this.props.books && this.props.books.length) { this.setPage(this.props.initialPage); } else { this.setState({ pager: {} }); this.props.onChangePage([]); } } } setPage(page) { const books = this.props.books; if (!books || books.length === 0) { this.setState({ pager: {} }); this.props.onChangePage([]); return; } const pageSize = this.state.pager.pageSize || 8; const totalPages = Math.ceil(books.length / pageSize); if (page < 1 || page > totalPages) { return; } const pager = this.getPager(books.length, page); const pageofBooks = books.slice(pager.startIndex, pager.endIndex + 1); this.setState({ pager: pager }); this.props.onChangePage(pageofBooks); } getPager(totalbooks, currentPage, pageSize) { currentPage = currentPage || 1; pageSize = pageSize || 8; const totalPages = Math.ceil(totalbooks / pageSize); let startPage, endPage; if (totalPages <= 10) { startPage = 1; endPage = totalPages; } else { if (currentPage <= 6) { startPage = 1; endPage = 10; } else if (currentPage + 4 >= totalPages) { startPage = totalPages - 9; endPage = totalPages; } else { startPage = currentPage - 5; endPage = currentPage + 4; } } const startIndex = (currentPage - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize - 1, totalbooks - 1); const pages = [...Array((endPage + 1) - startPage).keys()].map(i => startPage + i); return { totalbooks: totalbooks, currentPage: currentPage, pageSize: pageSize, totalPages: totalPages, startPage: startPage, endPage: endPage, startIndex: startIndex, endIndex: endIndex, pages: pages }; } render() { const pager = this.state.pager; if (!pager.pages || pager.pages.length <= 1) { return null; } return ( <ul className="pagination"> <li className={pager.currentPage === 1 ? 'disabled' : ''}> <a onClick={() => this.setPage(1)}>First</a> </li> <li className={pager.currentPage === 1 ? 'disabled' : ''}> <a onClick={() => this.setPage(pager.currentPage - 1)}>Previous</a> </li> {pager.pages.map((page, index) => <li key={index} className={pager.currentPage === page ? 'active' : ''}> <a onClick={() => this.setPage(page)}>{page}</a> </li> )} <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}> <a onClick={() => this.setPage(pager.currentPage + 1)}>Next</a> </li> <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}> <a onClick={() => this.setPage(pager.totalPages)}>Last</a> </li> </ul> ); } } Pagination.propTypes = propTypes; Pagination.defaultProps = defaultProps; /* App Component -------------------------------------------------*/ class App extends React.Component { constructor() { super(); axios.get(`https://goodreads-server-express--dotdash.repl.co/search/name`) .then(response => { this.setState({bookList: response.data.list}) ; }) .catch(error => { console.log(error); }); this.state = { bookList: [], pageofBooks: [], searchString: '' }; this.onChangePage = this.onChangePage.bind(this); this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } onChangePage(pageofBooks) { this.setState({ pageofBooks: pageofBooks }); } handleChange (e) { e.preventDefault(); this.setState({searchString: e.target.value}) } handleSubmit (e) { e.preventDefault(); if (!this.state.searchString) { alert('Oops! Please enter your search in the box below.') } else { this.setState({ bookList : [] }); axios.get(`https://goodreads-server-express--dotdash.repl.co/search/${this.state.searchString}`) .then(response => { this.setState({ bookList: response.data.list }); }) .catch(error => { alert("Yikes! Looks like we don't have anything for that search. Please edit your search and try again."); console.log(error); }); } } render() { return ( <div> <div className="container"> <div className="text-center"> <form className="search-bar"> <label htmlFor="search">Find me a book</label> <input id="search" onChange={this.handleChange} /> <button onClick={this.handleSubmit}>Search</button> </form> <div className="search-results"> {this.state.pageofBooks.map( (item, i) => <BookCard book={item} key={item.title} /> )} </div> <Pagination books={this.state.bookList} onChangePage={this.onChangePage} /> </div> </div> <hr /> </div> ); } } const BookCard = ({ book }) => { return ( <div className="book-card"> <div className="image__container"> <img src={book.imageUrl} /> </div> <div className="book-card__header"> <h3>{book.authorName}</h3> <h2>{book.title.length > 40 ? book.title.slice(0, 40) + '...' : book.title}</h2> </div> </div> ); }; ReactDOM.render(<App />, document.getElementById('app'));
内容的提问来源于stack exchange,提问作者Emily Grace Acres
相关产品推荐
相关产品推荐

