React子组件(Typescript)在第三次渲染之前不会保留状态

时间:2019-06-03 03:29:31

标签: reactjs typescript react-apollo

我有一个外部组件,其渲染方法如下:

    render()
    : JSX.Element 
    {
        return (
            <React.Fragment>
                <QueryGetCustomersPaginated
                    query={Q_GET_CUSTOMERS}
                    variables={{ limit: this.props.limit, offset: this.state.offset }}
                    pollInterval={1000}
                    onCompleted={()=>console.log('Data Loading Completed!')}
                >
                    {({ loading, error, data, startPolling, stopPolling }) =>
                    {
                        if (loading)
                        {
                            return "Loading..."
                        }
                        if (error)
                        {
                            return `Error: ${error.message}`
                        }
                        if (data)
                        {
                            // Pass Data to Private Properties
                            this.customers    = data.getCustomers.customers
                            this.totalRecords = data.getCustomers.metadata.totalRecords

                            // Build UI
                            return (
                                <React.Fragment>
                                    {/* PAGE TITLE */}
                                    <h2 className="text-center mb-3">Lista de Clientes</h2>

                                    {/* Customer List */}
                                    <ul className="list-group">
                                        {
                                            this.customers.map(customer => (
                                                <CustomerItem customer={(customer as Customer)} key={customer.id} />
                                            ))
                                        }
                                    </ul>

                                    {/* Pagination */}
                                    <Paginator
                                        maxRangeSize={3}
                                        pageSize={this.props.limit}
                                        totalRecords={this.totalRecords}
                                        currentPage={this.state.currentPage}
                                        onPageChange={(newOffset: number, newPage: number) => this.setPageFor(newOffset, newPage)}
                                    />                                    

                                </React.Fragment>
                            )
                        }
                    }}
                </QueryGetCustomersPaginated>
            </React.Fragment>
        )
    }

我还有一个子组件(Paginator)onPageChanged的事件处理程序,如下所示:

    private setPageFor(offset: number, page: number)
    {
        this.setState({
            offset      : offset,
            currentPage : page
        })
    }

最后,子组件如下所示:


import React, { SyntheticEvent }            from 'react'

//---------------------------------------------------------------------------------
// Component Class
//---------------------------------------------------------------------------------
export class Paginator extends 
    React.PureComponent<IPaginatorProps, IPaginatorState>
{
    //-------------------------------------------------------------------------
    constructor(props: IPaginatorProps)
    {
        // Calls Super
        super(props)

        // Initialize State
        this.state = {
            initialPageInRange  : 1,
            currentPage         : 1
        }
    }
    //-------------------------------------------------------------------------
    public render(): JSX.Element
    {
        return (
            <div className="row justify-content-center mt-3">
                <nav>
                    <ul className="pagination">

                        {this.renderPageItems()}

                    </ul>
                </nav>
            </div>   
        )
    }
    //-------------------------------------------------------------------------
    private renderPageItems(): JSX.Element[]
    {
        // Return Value
        const items: JSX.Element[] = []

        for (let iCounter: number = 0; iCounter < this.getMaxPossibleRange(); iCounter++)
        {
            items.push(
                // className won't be set until third time a link is clicked..
                <li key={iCounter} className={(**this.state.currentPage** === (this.state.initialPageInRange + iCounter)) ?
                    'page-item active' : 'page-item'}>

                    <a className="page-link"
                        href="#"
                        onClick={(e: SyntheticEvent) => this.goToPage(this.state.initialPageInRange + iCounter, e)}
                    >
                        {this.state.initialPageInRange + iCounter}
                    </a>
                </li>
            )
        }

        return items
    }
    //-------------------------------------------------------------------------
    private goToPage(page: number, e: SyntheticEvent)
    {
        e.preventDefault()

        const newOffset: number = this.getOffset(page)
        const newPage: number = this.getCurrentPage(newOffset)

        this.setState({
            currentPage: newPage
        })

        this.props.onPageChange(newOffset, newPage)
    }

    //-------------------------------------------------------------------------
    private getOffset(currentPage: number): number
    {
        return ((currentPage - 1) * this.props.pageSize)
    }
    //-------------------------------------------------------------------------
    private getCurrentPage(offset: number): number
    {
        return ((Math.ceil(offset / this.props.pageSize)) + 1)
    }
    //-------------------------------------------------------------------------
    private getTotalPages(): number
    {
        return (Math.ceil(this.props.totalRecords / this.props.pageSize))
    }
    //-------------------------------------------------------------------------
    private getMaxPossibleRange(): number
    {
        return (this.props.maxRangeSize <= this.getTotalPages()) ? 
                    this.props.maxRangeSize : this.getTotalPages()
    }
}

//---------------------------------------------------------------------------------
// Component Interfaces
//---------------------------------------------------------------------------------
interface IPaginatorProps
{
    maxRangeSize        : number  // 3
    pageSize            : number  // 3              
    totalRecords        : number  // 19
    currentPage         : number  // 1
    onPageChange        : (newOffset: number, newPage: number) => void
}
//---------------------------------------------------------------------------------
interface IPaginatorState
{
    initialPageInRange  : number
    currentPage         : number
}

如您所见,我们有一个主要组件,该组件发出带有偏移量参数(设置为状态)的Apollo查询,该参数在每次调用setPageFor(offset)时都会更新,以更新状态并重新呈现该组件。

现在,在子组件中,我有两种获取currentPage值的方法,一种是通过使用从父组件(在这种情况下为客户)传递的道具,另一种是使用组件的状态来设置初始值1并更新任何页面链接中的每次点击的状态。

单击后的页面链接将调用goToPage(),它会设置本地状态并触发将页面更改为调用方(客户组件)的事件。

此组件的当前行为是,直到第三次按下任何链接,才将<li>标记className更改为“ active”。

我尝试使用shouldComponentUpdate()并扩展React.PureComponent,甚至尝试设置超时(我从没写过...)来测试是否可行,但是结果总是一样。

现在,如果我在onPageChanged()方法中注释触发goToPage()事件的行,则分页的子组件呈现完美,但是如果我不这样做,则允许该子组件发送事件对于父级,此子级将重新渲染所有树,就像子组件已被重新安装导致其状态被删除一样。

奇怪的是,我第三次单击任何链接时,一切都按预期运行,但失败两次,第三次运行。

在周末的几个小时后,我用调试器分析此代码后,我对此感到完全困惑。

我很抱歉复制和粘贴所有这些代码,但是我认为有必要在此处了解我要实现的目标。

P.S。在这一点上,可以使用道具来解决该问题,该道具用于从客户组件传递currentPage属性,该客户组件是子组件的(用于分页)调用者。

我已经在React上工作了几个月了,这是我第一次遇到这个奇怪的错误,因此我真的很想知道为什么会发生这种行为以及为什么前两次状态会丢失组件渲染。同样,出于重用组件的目的,我认为最好将这两个变量保持在状态中。任何了解React更好的人的想法都会受到赞赏,还是Apollo库是问题所在?

谢谢。

0 个答案:

没有答案