React Subcomponent (Typescript) не сохраняет состояние, пока не будет отображен в третий раз - PullRequest
0 голосов
/ 03 июня 2019

У меня есть внешний компонент с методом рендеринга, подобным этому:

    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(), подкомпонент для разбивки на страницы отрисовывается отлично, но если я этого не сделаю, и субкомпоненту разрешено отправить событие вродительский объект перерисовывает все дерево, и это похоже на то, что подкомпонент был перемонтирован, что привело к удалению его состояния.

Странная часть в том, что в третий раз, когда я нажимаю на любую ссылку, все работает, как ожидалось, два раза выходит из строя и работает третий.

Я полностью озадачен этим моментом после нескольких часов моих выходных, анализируя этот код с помощью отладчика.

Я прошу прощения за копирование и вставку всего этого кода, но я считаю, что было необходимо получить представление о том, чего я пытаюсь достичь здесь.

PS На данный момент проблему можно решить, используя реквизиты для передачи свойства currentPage от компонента customer, который является вызывающей стороной подкомпонента (для разбиения на страницы).

Я работаю с React уже несколько месяцев, и это первый раз, когда я получаю и странно ошибаюсь, поэтому мне бы очень хотелось узнать, почему происходит такое поведение и почему состояние теряется.Первые два раза компонент рендерится.Также с целью повторного использования компонента, я считаю, что лучше хранить эти две переменные в состоянии.Любые мысли от тех, кто знает React лучше, будут высоко оценены, или это проблема библиотеки Apollo?

Спасибо.

...