Skip to content

ryanrozich/ag-grid-react-components

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

89 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

AG Grid React Components

πŸŽ‰ Pre-release: Modular Architecture with Minimal Bundle Size!

A collection of powerful, tree-shakeable React components for AG Grid (v33.3.0+) that enhance your data grid with advanced filtering and state management capabilities. Start with just 25KB or add features as needed.

πŸ“– Full Documentation β†’ πŸš€ Live Demo β†’

πŸš€ Features

πŸ“… Relative Date Filter

A custom date filter that supports both absolute dates and relative date expressions:

  • Dual Mode Support: Switch between date picker and expression mode
  • Relative Expressions: Use expressions like "Today+7d", "StartOfMonth-1M"
  • Comprehensive Operations: Equals, Not Equals, After, Before, In Range
  • Real-time Validation: Shows resolved dates for relative expressions

🎯 Quick Filter Dropdown

A dropdown component for applying predefined filters quickly:

  • Preset Options: Pre-configured filter options for common scenarios
  • Customizable: Define your own filter presets
  • Keyboard Navigation: Full keyboard support
  • Search Functionality: Search through options for large lists
  • Icons & Descriptions: Rich UI with icons and descriptions

πŸ” Active Filters Display

A component that displays active filters as removable pills:

  • Visual Feedback: Shows filter column names and values
  • Individual Removal: Click Γ— to remove specific filters
  • Clear All: Remove all filters with one click
  • Filter Types: Handles date, text, and set filters
  • Customizable: Style with CSS classes

πŸ”— URL State Persistence

Comprehensive grid state persistence with URL synchronization:

  • Full Grid State: Persists filters, columns, sorting, and grouping
  • URL Compression: Uses LZ-String for 50-90% smaller URLs
  • Browser History: Full back/forward navigation support
  • Shareable Links: Share complete grid configurations
  • Selective Persistence: Choose which state to include
  • Date Serialization: Properly handles Date objects

πŸ“¦ Installation

Choose your installation based on your needs:

Minimal Installation (25KB)

# Install the complete package (tree-shakeable)
npm install ag-grid-react-components

With React DatePicker (65KB)

# Install with peer dependencies for date picker
npm install ag-grid-react-components react-datepicker

Full Installation (85KB)

# Install with all optional dependencies
npm install ag-grid-react-components react-datepicker lz-string

πŸ“‹ Requirements

  • AG Grid Community or Enterprise v33.3.0+
  • React 18 or later
  • date-fns v4 or later

πŸ”§ Usage

Minimal Setup (25KB)

import { AgGridReact } from "ag-grid-react";
import { createDateFilter } from "ag-grid-react-components";

// Create DateFilter with native HTML5 inputs
const DateFilter = createDateFilter();

const columnDefs = [
  {
    field: "date",
    filter: DateFilter,
    floatingFilter: true,
  },
];

With React DatePicker (65KB)

import { createDateFilter, reactDatePickerAdapter } from "ag-grid-react-components";

// Create DateFilter with React DatePicker
const DateFilter = createDateFilter({
  datePickerAdapter: reactDatePickerAdapter,
});

Full Setup Example (85KB)

import { AgGridReact } from "ag-grid-react";
import { createDateFilter, createQuickFilterDropdown, createActiveFilters, setupGridStatePersistence, reactDatePickerAdapter } from "ag-grid-react-components";

// Create all components
const DateFilter = createDateFilter({
  datePickerAdapter: reactDatePickerAdapter,
});
const QuickFilterDropdown = createQuickFilterDropdown();
const ActiveFilters = createActiveFilters();

function App() {
  const [gridApi, setGridApi] = useState(null);

  const onGridReady = (params) => {
    setGridApi(params.api);
    // Enable compressed URL state persistence
    setupGridStatePersistence(params.api, {
      compressionAdapter: createLZStringAdapter(),
      useCompression: true,
    });
  };

  return (
    <div>
      <QuickFilterDropdown
        api={gridApi}
        columnId="date"
        options={[
          { id: "today", label: "Today" },
          { id: "week", label: "This Week" },
        ]}
      />

      <AgGridReact columnDefs={columnDefs} onGridReady={onGridReady} rowData={rowData} />
    </div>
  );
}

🌟 Bundle Sizes

Use Case Bundle Size
Just DateFilter (native) 25KB
With React DatePicker 65KB
All components 85KB

🎯 Key Features

Core Features (25KB minimal)

  • βœ… DateFilter with native HTML5 inputs
  • βœ… QuickFilterDropdown for preset filters
  • βœ… ActiveFilters display component
  • βœ… Tree-shakeable architecture

Optional Features (loaded on demand)

  • πŸ“… React DatePicker integration (+40KB when used)
  • πŸ—œοΈ LZ-String URL compression (+20KB when used)
  • 🎨 Pre-built styles (optional)
  • πŸ”Œ Full TypeScript support

#### Portal Rendering

The dropdown supports three rendering modes via the `usePortal` prop:

- **`"never"` (default)**: Uses CSS positioning. Best performance for most cases.
- **`"always"`**: Always renders using React Portal. Use when the dropdown is inside containers with `overflow: hidden`.
- **`"auto"`**: Automatically detects if portal is needed (experimental).

```tsx
// Example: Dropdown inside a scrollable container
<div style={{ overflow: "auto", height: "300px" }}>
  <QuickFilterDropdown
    api={api}
    columnId="status"
    options={statusOptions}
    usePortal="always" // Prevents clipping in scrollable container
  />
</div>

πŸ“š API Documentation

Date Filter

// Factory function with options
const DateFilter = createDateFilter({
  datePickerAdapter?: DatePickerAdapter,  // Optional date picker
  className?: string,                      // Custom CSS class
  styles?: DateFilterStyles               // Custom styles object
});

// Filter parameters
filterParams: {
  buttons?: ['reset', 'apply'],
  closeOnApply?: boolean,
  defaultMode?: 'absolute' | 'relative',
  dateFormat?: string,  // date-fns format

  // Inclusivity settings
  afterInclusive?: boolean,    // >= vs >
  beforeInclusive?: boolean,   // <= vs <
  rangeInclusive?: {
    from?: boolean,
    to?: boolean
  }
}

Quick Filter Dropdown

interface QuickFilterOption {
  id: string;
  label: string;
  icon?: string;
  description?: string;
  filterModel?: Record<string, unknown>;
  onSelect?: (api: GridApi) => void;
}

const QuickFilterDropdown = createQuickFilterDropdown();

<QuickFilterDropdown
  api={gridApi}
  columnId="date"
  options={options}
  placeholder="Select filter"
  showDescriptions={true}
  usePortal="never" | "always" | "auto"
/>

Grid State Persistence

import { setupGridStatePersistence } from "ag-grid-react-components";

// With compression (enabled by default)
const cleanup = setupGridStatePersistence(gridApi, {
  useCompression: true,
  maxUrlLength: 2000,

  includeFilters: true,
  includeColumns: true,
  includeSort: true,
  includeRowGrouping: true,

  onStateLoad: (state) => console.log("Loaded:", state),
  onStateSave: (state) => console.log("Saved:", state),
});

Advanced Examples

Selective State Persistence
// Only persist specific state elements
const cleanup = setupGridStatePersistence(gridApi, {
  includeFilters: true,
  includeColumns: false, // Don't persist column changes
  includeSort: true,
  includeRowGrouping: false, // Don't persist grouping
});
Manual State Management
import { captureGridState, applyGridState } from "ag-grid-react-components";

// Capture current state
const state = captureGridState(gridApi, {
  includeFilters: true,
  includeColumns: true,
});

// Save to localStorage
localStorage.setItem("gridState", JSON.stringify(state));

// Restore from localStorage
const savedState = localStorage.getItem("gridState");
if (savedState) {
  const state = JSON.parse(savedState);
  applyGridState(gridApi, state);
}

// Send to server
const saveToServer = async () => {
  const state = captureGridState(gridApi);
  await fetch("/api/grid-state", {
    method: "POST",
    body: JSON.stringify(state),
  });
};

Compression Effectiveness

LZ-String compression provides significant URL length reduction:

State Type Original Compressed Reduction
Simple filters 312 chars 88 chars 72%
Complex grid state 2,890 chars 342 chars 88%
10 column definitions 1,245 chars 156 chars 87%

Example compressed URL:

https://app.com/?gridState=N4IgZgpgLghgbgUwHZQKYQPYFMCeEB0IA5gMYD2AdAK4C2E...

State Persistence Options

You can choose between filter-only persistence or full grid state persistence:

// Option 1: Filter persistence only (lightweight)
import { setupFilterStatePersistence } from "ag-grid-react-components";
setupFilterStatePersistence(params.api);

// Option 2: Full grid state persistence (recommended)
import { setupGridStatePersistence } from "ag-grid-react-components";
setupGridStatePersistence(params.api, {
  useCompression: true,
  includeFilters: true,
  includeColumns: true,
  includeSort: true,
});

Custom State Handlers

// Example: Save to server with short ID
const setupServerStatePersistence = (gridApi) => {
  return setupGridStatePersistence(gridApi, {
    maxUrlLength: 100, // Force server storage for long states

    onStateSave: async (state) => {
      const url = new URL(window.location);

      if (JSON.stringify(state).length > 100) {
        // State too large for URL, save to server
        const response = await fetch("/api/grid-state", {
          method: "POST",
          body: JSON.stringify(state),
        });
        const { id } = await response.json();

        // Use short ID in URL
        url.searchParams.set("stateId", id);
      } else {
        // Small state, keep in URL
        url.searchParams.set("gridState", JSON.stringify(state));
      }

      window.history.replaceState({}, "", url);
    },

    onStateLoad: async (state) => {
      const url = new URL(window.location);
      const stateId = url.searchParams.get("stateId");

      if (stateId) {
        // Load from server
        const response = await fetch(`/api/grid-state/${stateId}`);
        return await response.json();
      }

      return state;
    },
  });
};

πŸ“… Advanced DateFilter Features

Open-Ended Date Ranges

The DateFilter now supports open-ended date ranges, allowing you to filter dates with only a start or end date:

// Filter all dates after January 1, 2024 (no end date)
const filterModel = {
  type: "inRange",
  mode: "absolute",
  dateFrom: new Date("2024-01-01"),
  dateTo: null, // Open-ended to future
};

// Filter all dates before December 31, 2024 (no start date)
const filterModel = {
  type: "inRange",
  mode: "absolute",
  dateFrom: null, // Open-ended from past
  dateTo: new Date("2024-12-31"),
};

// With relative expressions
const filterModel = {
  type: "inRange",
  mode: "relative",
  expressionFrom: "Today-30d",
  expressionTo: null, // All dates from 30 days ago onwards
};

Inclusive/Exclusive Date Filtering

Control whether date boundaries are inclusive or exclusive for precise filtering:

const columnDefs = [
  {
    field: "date",
    filter: RelativeDateFilter,
    filterParams: {
      // Make 'after' filter inclusive (>= instead of >)
      afterInclusive: true,

      // Make 'before' filter inclusive (<= instead of <)
      beforeInclusive: true,

      // Control inclusivity for date ranges
      rangeInclusive: {
        from: true, // Include start date (>=)
        to: true, // Include end date (<=)
      },
    },
  },
];

You can also set inclusivity per filter instance:

// Programmatically set filter with specific inclusivity
api.setFilterModel({
  dateColumn: {
    type: "inRange",
    mode: "absolute",
    dateFrom: new Date("2024-01-01"),
    dateTo: new Date("2024-12-31"),
    fromInclusive: true, // Include January 1st
    toInclusive: false, // Exclude December 31st
  },
});

Inclusivity Examples

  • Exclusive (default): after 2024-01-01 matches dates > 2024-01-01 (2024-01-02 onwards)
  • Inclusive: after 2024-01-01 with afterInclusive: true matches dates >= 2024-01-01 (includes 2024-01-01)
  • Range exclusive: 2024-01-01 to 2024-01-31 matches dates > 2024-01-01 and < 2024-01-31
  • Range inclusive: 2024-01-01 to 2024-01-31 with rangeInclusive: {from: true, to: true} matches dates >= 2024-01-01 and <= 2024-01-31

πŸ“š Date Expression Syntax

The Relative Date Filter supports powerful expressions for dynamic date filtering. See the comprehensive Date Expressions documentation for full details.

Quick Reference

  • Basic: Today, StartOfWeek, EndOfMonth, StartOfYear
  • Arithmetic: Today+7d, Today-30d, Today+1m, Today-1y
  • Period Start/End: StartOfMonth, EndOfYear, StartOfWeek, EndOfWeek

Units

  • d - days
  • w - weeks
  • m - months
  • y - years

πŸ“– Full Documentation β†’

🎨 Customization

See the comprehensive Styling Guide for detailed customization options.

Quick Start

The components use CSS variables for easy theming:

:root {
  --agrc-primary: #2563eb;
  --agrc-border: #e5e7eb;
  --agrc-hover: #f3f4f6;
}

Custom Quick Filter Options

const customOptions = [
  {
    id: "all",
    label: "All Items",
    filterModel: null, // Clears filter
  },
  {
    id: "recent",
    label: "Last 7 Days",
    filterModel: {
      mode: "relative",
      type: "inRange",
      expressionFrom: "Today-7d",
      expressionTo: "Today",
    },
  },
];

Styling

The components use CSS modules and can be customized via CSS variables:

:root {
  --agrc-primary: #2563eb;
  --agrc-border: #e5e7eb;
  --agrc-hover: #f3f4f6;
}

πŸ§ͺ Testing

Components are thoroughly tested with:

  • Unit tests (Vitest + React Testing Library)
  • Integration tests with AG Grid
  • E2E tests (Playwright)

⚠️ Known Issues

AG Grid v33 setFilterModel Bug

When calling api.setFilterModel() programmatically on custom React filter components in AG Grid v33, the filter doesn't properly initialize. This is a known AG Grid bug affecting all v33.x versions.

Related Issues:

Workaround:

Use the provided applyFilterModelWithWorkaround function:

import { applyFilterModelWithWorkaround } from "ag-grid-react-components";

// Instead of:
api.setFilterModel({ dateColumn: filterModel });

// Use:
await applyFilterModelWithWorkaround(api, "dateColumn", filterModel);

This workaround handles AG Grid v33's Promise-based filter instances and ensures the filter state is properly initialized. It should be removed once AG Grid fixes the underlying issue.

πŸš€ Demo

Live demo available at: https://demo.rozich.net/ag-grid-react-components/

The demo is deployed using a custom Cloudflare Workers architecture. See:

πŸ“„ License

MIT

🀝 Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

πŸ“‹ Issues & Project Management

When creating issues, our automation will sync labels to project fields for better organization.

For AI Agents & Automation