ag-grid / ag-grid

The best JavaScript Data Table for building Enterprise Applications. Supports React / Angular / Vue / Plain JavaScript.
http://www.ag-grid.com
Other
12.7k stars 1.86k forks source link

Passing valueFormatter information to a reusable Grid component #7397

Closed mikevarela closed 8 months ago

mikevarela commented 9 months ago
[] bug report => see 'Providing a Reproducible Scenario'
[] feature request => do not use Github for feature requests, see 'Customers of AG Grid'
[x] support request => see 'Requesting Community Support'

Hi, using NextJS 14 and wanting to create a reusable Grid component. Forgive the noob question, but, I'd defining my column defs in a page and passing those to the reusable grid component. I've created some utility functions to format a phone number and format title case which I'd like to use in the valueFormatter part of the columnDefs. I'm unable to pass that function to the client agGrid reusable component, can't serialize a function. How would someone go about adding formatter information in a parent component that is calling a client agGrid 'use client' component?

Parent Component

import formatPhoneNumber from "@/utils/formatPhoneNumber";
import formatTitleCase from "@/utils/formatTitleCase";

const columns = [
  {
    field: "name",
    headerName: "NAME",
    filter: true,
    sortable: true,
    valueFormatter: formatTitleCase,
  },
  {
    field: "phone",
    headerName: "PHONE",
    valueFormatter: formatPhoneNumber,
  },
  {
    field: "website",
    headerName: "WEBSITE",
  },
];

export default async function AgenciesPage() {
  const data: any = await getAgencies();

  return (
    <>
      <ActionBar>
        <Link href={paths.agencyAdd()}>
          <Button variant="primary">Add Agency</Button>
        </Link>
      </ActionBar>
      <Paper>
        <AGGridTable columns={columns} rows={data} />
      </Paper>
    </>
  );
}

Child Component (Reusable Grid Component)

type GridProps = {
  columns: AGGridColumnType[];
  rows: any[];
};

export default function AGGridTable({ columns, rows }: GridProps) {
  const router = useRouter();

  const [columnDefs, setColumnDefs] = useState(columns);
  const [rowData, setRowData] = useState(rows);

  // Optional - for accessing Grid's API
  const gridRef = useRef<AgGridReact<any>>(null);

  // DefaultColDef sets props common to all Columns
  const defaultColDef = useMemo(
    () => ({
      flex: 1,
      minWidth: 100,
      sortable: true,
    }),
    []
  );

  const handleRowClick = useCallback((event: any) => {
    router.push(paths.agency(event.data.id));
  }, []);

  return (
    <div className="mx-auto">
      {/* On div wrapping Grid a) specify theme CSS Class Class and b) sets Grid size */}
      <div className="ag-theme-alpine h-[calc(100vh-15rem)]">
        {/* <div className="ag-theme-alpine w-[calc(100vw-17rem)] h-[calc(100vh-10rem)]"> */}
        <AgGridReact
          ref={gridRef} // Ref for accessing Grid's API
          rowData={rowData} // Row Data for Rows
          columnDefs={columnDefs} // Column Defs for Columns
          defaultColDef={defaultColDef} // Default Column Properties
          animateRows={true} // Optional - set to 'true' to have rows animate when sorted
          rowSelection="single" // Options - allows click selection of rows
          pagination={true}
          paginationPageSize={50}
          onCellClicked={handleRowClick}
        />
      </div>
    </div>
  );
}
joemaylor commented 9 months ago

Hi @mikevarela

Not the best solution but you might consider using Column Types for this. Not super familiar with React but I think something like the below would work.

Note this would mean that the column types are being imported and registered for every instance of your reusable grid component.

Your parent component:

const columns = [
  {
    field: "name",
    headerName: "NAME",
    filter: true,
    sortable: true,
    type: 'titleColumn',
  },
  {
    field: "phone",
    headerName: "PHONE",
    type: 'phoneNumberColumn',
  },
  {
    field: "website",
    headerName: "WEBSITE",
  },
];

export default async function AgenciesPage() {
  const data: any = await getAgencies();

  return (
    <>
      <ActionBar>
        <Link href={paths.agencyAdd()}>
          <Button variant="primary">Add Agency</Button>
        </Link>
      </ActionBar>
      <Paper>
        <AGGridTable columns={columns} rows={data} />
      </Paper>
    </>
  );
}

And your child component:

import formatPhoneNumber from "@/utils/formatPhoneNumber";
import formatTitleCase from "@/utils/formatTitleCase";

type GridProps = {
  columns: AGGridColumnType[];
  rows: any[];
};

export default function AGGridTable({ columns, rows }: GridProps) {
  const router = useRouter();

  const [columnDefs, setColumnDefs] = useState(columns);
  const [rowData, setRowData] = useState(rows);

  // Optional - for accessing Grid's API
  const gridRef = useRef<AgGridReact<any>>(null);

  // DefaultColDef sets props common to all Columns
  const defaultColDef = useMemo(
    () => ({
      flex: 1,
      minWidth: 100,
      sortable: true,
    }),
    []
  );

  const handleRowClick = useCallback((event: any) => {
    router.push(paths.agency(event.data.id));
  }, []);

  const columnTypes = {
    titleColumn: { valueFormatter: formatTitleCase },
    phoneNumberColumn: { valueFormatter: formatPhoneNumber }
  }

  return (
    <div className="mx-auto">
      {/* On div wrapping Grid a) specify theme CSS Class Class and b) sets Grid size */}
      <div className="ag-theme-alpine h-[calc(100vh-15rem)]">
        {/* <div className="ag-theme-alpine w-[calc(100vw-17rem)] h-[calc(100vh-10rem)]"> */}
        <AgGridReact
          ref={gridRef} // Ref for accessing Grid's API
          rowData={rowData} // Row Data for Rows
          columnDefs={columnDefs} // Column Defs for Columns
          columnTypes={columnTypes} // Column Types
          defaultColDef={defaultColDef} // Default Column Properties
          animateRows={true} // Optional - set to 'true' to have rows animate when sorted
          rowSelection="single" // Options - allows click selection of rows
          pagination={true}
          paginationPageSize={50}
          onCellClicked={handleRowClick}
        />
      </div>
    </div>
  );
}
AG-Zoheil commented 8 months ago

Thank you for submitting this on Github.

As this has been open without any further interaction for a while, I will be closing this issue.

If the issue has already been resolved, you can ignore this. If however, you are still in need of support you can choose to raise the issue again for the AG Grid community to support.

Kind regards, Zoheil