You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 00:50:22