1

I have an application that makes requests for an API and has thousands of results. The API works with limit offset type pagination. That is, I pass as a parameter the limit (how many records I want to bring) and the offset (from which record I want to bring).

The problem is that the database has thousands of results per day. About 150k of results. That is, whenever I update the state of React to add the new results, I'm having a performance problem, because React renders all the data all over again.

Below is part of the code.

async getHits(filters, resetContent = false) {
        if(resetContent) {
            this.setState({
                isLoading: true
            })
        }
        try {
            const { data: { result } } = await API_CONFIG.get(`/hit${filters}`);

            const formattedHits = formatHits(result);

            let current;
            if(resetContent) {
                current = [...formattedHits];
            } else {
                current = [...this.state.hits, ...formattedHits]
            }

            this.setState(prevState => ({
                hits: current,
                currentOffset: resetContent ? 1 + DEFAULT_PAGINATION : prevState.currentOffset + DEFAULT_PAGINATION
            }));

        } catch (e) {
            this.setState({
                hits: [],
                error: 'Houve um erro durante a requisição. Por favor, recarregue e tente novamente.'
            });
        } finally {
            this.setState({
                isLoading: false,
                isLoadMoreLoading: false
            })
        }
    }

For example, when I click to load more, I change the state to set 'isLoadMoreLoading' to true, so I change the text from Carregar mais (Load more in portuguese) to Carregando (Loading in portuguese). This procedure takes a long time, as React is updating the entire state again.

Here I render the content:

const areEqual = (nextProps, prevProps) => {
    if(nextProps === prevProps) {
        return true
    };

    return false;
};

const TableResults = props => {
    const { hits, isLoading, error } = props;
    return (
        <Table>
            <thead>
            <tr>
                <TableHeader>Data</TableHeader>
                <TableHeader>Parceiro</TableHeader>
                <TableHeader>ID Arquivo</TableHeader>
                <TableHeader>Nome do Cliente</TableHeader>
                <TableHeader>Regra Afetada</TableHeader>
                <TableHeader>Valor ME</TableHeader>
                <TableHeader>Merchant Final</TableHeader>
                <TableHeader>País Merchant Final</TableHeader>
                <TableHeader>Gerador do Hit</TableHeader>
            </tr>
            </thead>

            <tbody>
            {isLoading && (
                <tr>
                    <td className="content-not-allowed" colSpan={9}>
                        <Loader />
                    </td>
                </tr>
            )}

            {!isLoading && error && (
                <tr>
                    <td className="content-not-allowed not-found" colSpan={9}>
                        <span>{error}</span>
                    </td>
                </tr>
            )}

            {!isLoading && !hits.length && !error && (
                <tr>
                    <td className="content-not-allowed not-found" colSpan={9}>
                        <span>Sem resultados, revise sua busca.</span>
                    </td>
                </tr>
            )}

            {!isLoading && !!hits.length && !error && (
                hits.map(hit => (
                    <tr onClick={e => {
                        e.preventDefault();
                        props.history.push('/compliance/operations/'+hit.hit_id)
                    }} key={hit.hit_id}>
                        <td>{hit.created_at || '-'}</td>
                        <td>{hit.partner || '-'}</td>
                        <td>{hit.file_id || '-'}</td>
                        <td>{hit.cliente_nome || '-'}</td>
                        <td>{renderHitGenerator(hit.compliance_integration).description}</td>
                        <td>{hit.valorme || '-'}</td>
                        <td>{hit.merchant_nome_final || '-'}</td>
                        <td>{hit.merchant_pais_final || '-'}</td>
                        <td>{renderHitGenerator(hit.compliance_integration).name}</td>
                    </tr>
                ))
            )}
            </tbody>
        </Table>
    )
}

export default withRouter(React.memo(TableResults, areEqual));

I looked on the internet to optimize the results for this amount of data, but I didn't find anything satisfactory.

How can I update the state of React with this amount of data and still be a performer.

Thanks.

  • Looks like either (or multiple) of the following issues that are causing perfomance problems: 1. Are you sure you are sending the limit and offset to the API call? 2. You are setting state twice. Once after your results come back and once in the `finally` block. Which you can do in one go. Same with the `error` block. 3. Do check if you are using the `key` prop in your repeated components. This is important to prevent re-rendering data. – ashsav Mar 17 '20 at 15:26
  • Do you render the array components inside the same component? – Burak Gavas Mar 17 '20 at 15:31
  • Thanks for the replies. @Ashwin Responding: 1. Yes, I am sending the limit inside the variable "filters". 2. I set the status 2 times, because, regardless of whether or not I managed to bring the API response, I have to remove the loader. 3. Yes, I pass 1 key for each rendering line. Burak I render in another component with React.memo. I'll put that block of code in the question. My problem is not to bring the data. But I have to add more and more data to my state and it is getting heavy. – Avallon Azevedo Mar 17 '20 at 16:49
  • First Memo is no use, since areEqual uses === to check only object instance equal. it will be always false. – xdeepakv Mar 17 '20 at 17:24
  • Another thing, you can remove isLoading and error out of check. Make them separate from hits.length . Mainly showing loader and error inside table use some div or popup spinner out of table to visually look faster, even backend slow – xdeepakv Mar 17 '20 at 17:27
  • Use React Virtualized List in place of a table. The reason is let's suppose there 10k records and you scroll down till 10k record, so your DOM will have 10k table rows. But if you use React Virtualized, at any given point of time, your DOM will load only those rows which are visible on the screen, some 20-25 at max. – Nayan shah Mar 17 '20 at 17:38

1 Answers1

0

Make sure each element in the array is mapped to a unique and deterministic key. Once that is true, you can use shouldComponentUpdate to make sure that components are only rerendered if they are different.

You can also look into libraries like react-infinite to limit the amount of items rendered at once to improve performance.

Finally, since you are using React Router it is important that you are using the render method instead of the component method to prevent rerenders. See this SO post for more info.