'Making an api call inside a function causing the browser to freeze in react (typescript)

I have a typescript file and having some issues ( I believe re-rendering) inside the cellsrenderer function as shown below:

cellsrenderer: (row, columnfield, value, defaulthtml, columnproperties): string => {

         axios
            .get("api/personnels/"+value) 
               .then(response => {
                  this.setState({
                    createdByName: response.data.displayedValues 
                 }, ()=> {
               console.log('Inside axios response after setting the state to the name of the project creater')
                 })
                }).catch(err => console.log(err));


                return this.state.createdByName;

                }

When my code runs, I keep seeing the console.log('Inside axios response after setting the state to the name of the project creater') in console for many times even though I have only 30 records to display in total. The browser freezes at some point and I have to close it forcefully. It's happening only because of an API call that I am making inside cellsrenderer function shown above. If I just do the following, everything works fine:

cellsrenderer: (row, columnfield, value, defaulthtml, columnproperties): string => {
    
            return value;
    
}

What issue is with the API call inside cellsrenderer function which is causing the browser to freeze?

In chrome I am seeing the following:

enter image description here

And then it throws in the browser net::ERR_INSUFFICIENT_RESOURCES related error:

GET https://myserver.com/api/personnels/12345 net::ERR_INSUFFICIENT_RESOURCES
dispatchXhrRequest @ xhr.js:160
xhrAdapter @ xhr.js:11
dispatchRequest @ dispatchRequest.js:59
Promise.then (async)
request @ Axios.js:51
Axios.<computed> @ Axios.js:61
wrap @ bind.js:9
cellsrenderer @ Projects.tsx:261
_rendercell @ jqxgrid.js:8
_rendervisualcell @ jqxgrid.js:8
_rendervisualrows @ jqxgrid.js:8
l @ jqxgrid.js:8
_renderrows @ jqxgrid.js:8
rendergridcontent @ jqxgrid.js:8
_render @ jqxgrid.js:8
dataview.update @ jqxgrid.js:8
q @ jqxgrid.js:8
l @ jqxgrid.js:8
callDownloadComplete @ jqxdata.js:8
success @ jqxdata.js:8
bw @ jqxcore.js:8
fireWith @ jqxcore.js:8
S @ jqxdata.js:8
H @ jqxdata.js:8
Projects.tsx:269 Error: Network Error
    at createError (createError.js:16:15)
    at XMLHttpRequest.handleError (xhr.js:69:14)

Here is my complete code:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {FormikApp} from './forms/AddProjectForm'
import JqxGrid, {IGridProps, jqx} from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid';
import JqxButton from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxbuttons'
import {RouteComponentProps} from 'react-router-dom'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.base.css'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.material.css'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.arctic.css'
import {Dialog} from "primereact/dialog";
import {Button} from "primereact/button";
import {properties} from "../properties";
import {Card} from "primereact/card";
import axios from "axios";
import {Messages} from "primereact/messages";
import _ from 'lodash'

export interface IState extends IGridProps {
    projects: [],
    selectedProject: [],
    createdByName :string,
    addDialogVisible: boolean,
    blazerId: string,
    username: string,
    selectedRowIndex: number,
    deleteDialogVisible: boolean
}

class Projects extends React.PureComponent<RouteComponentProps<{}>, IState> {
    private baseUrl = properties.baseUrlWs
    private myGrid = React.createRef<JqxGrid>()
    private messages = React.createRef<Messages>()

    private editrow: number = -1;

    constructor(props: RouteComponentProps) {
        super(props);

        this.selectionInfo = this.selectionInfo.bind(this)
       
        this.gridOnSort = this.gridOnSort.bind(this);

        

      const columns: IGridProps['columns'] = [
            { text: 'Project Name', datafield: 'name', width: 390 },
            { text: 'Project Description', datafield: 'description', width: 390 },
            { text: 'Owner Assigned', datafield: 'institutionId', width: 180,hidden:true },
            { text: 'Created By',  datafield: 'createdBy', 
              
                                  
                cellsrenderer: (row, columnfield, value, defaulthtml, columnproperties): string => {

                            axios
                            .get("api/personnels/"+value) 
                            .then(response => {
                              
                                    this.setState({
                                        createdByName: response.data.displayedValues 
                                    }, ()=> {
                                        console.log('Inside axios response after setting the state to the name of the project creater')
                                    })
                                }).catch(err => console.log(err));
                            
                
                return this.state.createdByName;

                }
               
            }
           
            ]

        const source:any = {
            dataFields: [
                { name: 'id', type: 'long'},
                { name: 'name', type: 'string' },
                { name: 'description', type: 'string' },
                { name: 'url', type: 'string'},
                { name: 'irbProtocol', type: 'string'},
                { name: 'institutionId', type: 'long' },
                { name: 'projectType', type: 'string' },
                { name: 'priority', type: 'string'},
                { name: 'researchDataSetType', type: 'string'},
                { name: 'statusIndicatorId', type: 'long'},
                { name: 'createdBy', type: 'string' }
            ],
            dataType: 'json',
            root: 'projects',
            sortColumn: 'name',
            sortdirection: 'asc',
            url: this.baseUrl + 'api/projects/search/getProjectsById',
            data: {
                value: ''
            }
        }

        const dataAdapter:any = new jqx.dataAdapter(source,
            {
                autoBind: true,
                downloadComplete: (data:any, status:any, xhr:any):void => {
                    // if (!source.totalrecords) {
                        source.totalrecords = parseInt(data['page'].totalElements);
                    // }
                },
                formatData: (data:any):any => {
                  
                    data.page = data.pagenum
                    data.size = data.pagesize
                    if (data.sortdatafield && data.sortorder) {
                        data.sort = data.sortdatafield + ',' + data.sortorder;
                    }
                    return data;
                },
                loadError (xhr, status, error) {
                    throw new Error('Error occurred in getting Projects for user ' + error.toString());
                }
            }
        );

        

        this.state = {
            projects: [],
            selectedProject: [],
            createdByName : '',
            blazerId: '',
            username: '',
            addDialogVisible: false,
            selectedRowIndex: null,
            deleteDialogVisible: false,
            columns: columns,
            rendergridrows: (params: any): any[] => {
                const data = params.data
                return data;
            },
            source: dataAdapter,
           
        };
    }

    setValueProperty = (data:any):any => {
        if (this.state && this.state.blazerId) {
            data.value = this.state.blazerId
        }
    }

   

    private gridOnSort(event: any): void {
        const sortinformation = event.args.sortinformation;
        let sortdirection = sortinformation.sortdirection.ascending ? 'ascending' : 'descending';
        if (!sortinformation.sortdirection.ascending && !sortinformation.sortdirection.descending) {
            sortdirection = 'null';
        }
        this.myGrid.current.updatebounddata('sort')
    };

    selectionInfo = (event: any): void => {
        const selection = this.myGrid.current.getrowdata(event.args.rowindex)

       this.setState({
            selectedProject: selection
        }, () => {
            console.log('pushing ' + this.state.selectedProject)
            this.props.history.push({
                pathname: '/project',
                state: {
                    project: this.state.selectedProject,
                    blazerId: this.state.blazerId
                }
            })
        });
    }

   

   

   
    componentDidMount() {
        console.log('In Projects.componentDidMount....' + sessionStorage.getItem('loggedInUser'))
        if (sessionStorage.getItem('loggedInUser') != null) {
            const loggedInUser = JSON.parse(sessionStorage.getItem('loggedInUser') as string)
            this.setState({ employeeId: loggedInUser.employeeId})
        }
    }

    render() {
        

        const defaultView = this.state.addDialogVisible ? null : (this.state.employeeId && !_.isEmpty(this.state.employeeId)) ? (
            <div style={{width: '100%', margin: '0 auto', display: 'table'}}>
                <JqxGrid
                    // @ts-ignore
                    ref={this.myGrid}
                    theme={'arctic'}
                    altrows={true}
                    width="100%"
                    autoheight={true}
                    source={this.state.source}
                    columns={this.state.columns}
                    pageable={true}
                    sortable={true}
                    onSort={this.gridOnSort}
                    pagesize={20}
                    virtualmode={true}
                    rendergridrows={this.state.rendergridrows}
                    showtoolbar={true}
                    rendertoolbar={this.state.rendertoolbar}
                    columnsresize={true}/>
            </div>
        ) : null

       

        return (
            <div className="project-page-main">
            <Messages ref={this.messages} style={{width: '100%', margin: 'auto' }}/>
            <div className="content">
               
                {defaultView}
              
            </div>
            </div>
        );
    }
}
export default Projects;


Solution 1:[1]

I removed all the code that wasn't necessary to the explanation.

This is just to give you an idea of how you could move your api call in componentDidMount. There might be some modifications to make since I don't use jQuery with React I might have make false assumptions. In particular, I assumed you could know what values will be fetched in advance.

In you cellrenderer function you will get the value directly from the state:

cellsrenderer: (
            row,
            columnfield,
            value,
            defaulthtml,
            columnproperties
        ): string => {
                if(typeof this.state?.createdByName[value] === 'undefined') return ''
                return this.state?.createdByName[value];
        },

A getCreatedByName function to call the api.

getCreatedByName = async (value: string) => {
    try {
        const response = await axios.get('api/personnels/' + value);
        return { response: response.data.displayedValues, value };
    } catch (error) {
        console.error(error);
        return { response: 'error', value };    
    }
}

On componentDidMount you call your api with your values. You wait for all the api call to resolve so that you don't update the state multiple times.

componentDidMount() {
    let promises: Promise<{ value: string; response: string }>[] = [];
    const values = [`Value1`, 'Value2'];
    values.forEach((value) => {
        promises.push(this.getCreatedByName(value));
    });

    Promise.all(promises).then((responses) => {
        const createdByName: any = {};

        responses.forEach((response) => {
            createdByName[response.value] = response.response;
        });

        this.setState(
            {
                createdByName,
            },
            () => {
                console.log(
                    'Inside axios response after setting the state to the name of the project creater'
                );
            }
        );
    });
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1