Search code examples
reactjsag-gridag-grid-react

How can I autosize React AG-Grid columns?


I have a React app using AG-Grid that looks like this:

User interaction: The user selects a couple filter values from the dropdowns and clicks 'Get Data'. The app then sends a request to the backend, which sends a request to an external API to retrieve data based on the chosen filter values. The grid is then populated on the frontend.

Question: How can I efficiently automatically resize the columns (to fit the content within them) every time I re-render the grid with new data (i.e. every time the 'get data' button is pressed)?

My Code for Grid.jsx

import { AgGridReact } from 'ag-grid-react';
import { useEffect, useState, useMemo, useRef, useCallback } from 'react';

import 'ag-grid-community/styles/ag-grid.css'; // Core grid CSS, always needed
import 'ag-grid-community/styles/ag-theme-alpine.css'; // Optional theme CSS, comment out for custom styling
import './Grid.css';

const Grid = ({ gridData }) => {
    const gridRef = useRef();

    // Columns definition
    const [columnDefs, setColumnDefs] = useState([]);

    // Data/rows definition
    const [rowData, setRowData] = useState([]);

    // Transform grid data into format ag-grid can use
    const transformGridData = () => {
        const { columns, rows, cells } = gridData;
        const transformedColumnDefs = [];
        const transformedRowData = [];
        
        // ...

        // (omitted) A BUNCH OF LOGIC FOR PARSING THE DATA AND CONVERTING IT INTO AG-GRID's COLS AND ROWS
        
        // ...

        // Set column defs and row data
        setColumnDefs(transformedColumnDefs);
        setRowData(transformedRowData);
    }

    // Default column style/functionality definition
    const defaultColDef = useMemo(() => ({
        // default col settings
    }), []);

    // Update the grid when data given by external API changes
    useEffect(() => {
        if (gridData != null) {
            transformGridData();
        }
    }, [gridData]);

    return (
        <div className="ag-theme-alpine grid-div" style={{ height: 600, marginTop: 50, marginLeft: 17 }}>
            <AgGridReact
                ref={gridRef}
                rowData={rowData}
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
            />
        </div>
    )
}

export default Grid;

What I've tried:

  1. I built a function for autosizing all columns in the grid:
const autoSizeAll = useCallback(() => {
        const allColumnIds = [];
        gridRef.current.columnApi.getColumns().forEach((column) => {
            allColumnIds.push(column.colId);
        });

        gridRef.current.columnApi.autoSizeColumns(allColumnIds);
}, []);
  1. I ran this function upon the first render and every time the grid columns were somehow re-displayed. This worked, but (understandably) caused the app's performance to plummet.
return (
    <div className="ag-theme-alpine grid-div" style={{ height: 600, marginTop: 50, marginLeft: 17 }}>
        <AgGridReact
            ref={gridRef}
            rowData={rowData}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            onFirstDataRendered={autoSizeAll}
            onDisplayedColumnsChanged={autoSizeAll}
            suppressColumnVirtualization={true}
        />
    </div>
    )
}
  1. I also tried removing onFirstDataRendered and onDisplayedColumnsChanged and instead manually calling autoSizeAll() immediately after calling setColumnDefs(transformedColumnDefs) and setRowData(transformedRowData) in transformGridData(), like so:
        ...

        // Set column defs and row data
        setColumnDefs(transformedColumnDefs);
        setRowData(transformedRowData);

        autoSizeAll();
    }

This resulted in some very odd behavior. Clicking 'get data' once would populate the grid, but not autosize the columns. Clicking it again, however, would properly autosize the columns. This works the same way if I change the chosen filter values. First click populates, the second click autosizes.

Is there a solution to this problem that would work efficiently?


Solution

  • The setTimeout() solution suggested by Ahmet worked, but it felt a bit risky relying on an arbitrary timeout. I also did some research and found there was no real way to check if an AG-grid component was done rendering (besides onFirstDataRendered). Additionally, I also noticed a small "shifting" effect where the grid would be displayed on screen, and a split-second later all the columns would shift as they autosized to fit their data. This is of course because the autosizing would occur after the columns were rendered.


    This led me to try manually autosizing columns, which has been working much better so far.

    I have a measureTextWidth function, which determines the pixel length of a string based on my defined fontSize and fontFamily variables:

    // Measures the pixel width of a string
    const measureTextWidth = (text) => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        context.font = `${fontSize} ${fontFamily}`;
        return context.measureText(text).width;
    };
    
    

    As I build my rows and columns (in temporary variables), I update a dictionary called maxLengths that stores column labels as keys and the lengths of their longest cell content as values.

    In my case, that looks something like this:

    // Update max content length for this column (for autosizing)
    maxLengths[cell.formattedColCoordinates] = Math.max(
        maxLengths[cell.formattedColCoordinates] || 0,
        measureTextWidth(cell.formattedValue)
    );
    
    

    Finally, I update all the column widths before giving them and the row data to columnDefs and rowData:

    // Autosize: Set column widths according to max content length
    transformedColumnDefs.forEach((columnDef) => {
        columnDef.width = maxLengths[columnDef.field] + cellPadding;
    });
    
    // Set column defs and row data
    setColumnDefs(transformedColumnDefs);
    setRowData(transformedRowData);
    
    

    This approach has led to consistent, smooth and fast autosizing (no need to set suppressColumnVirtualization={true} and reduce my grid performance).