
Syncfusion React Grid
- 498 installs
- 3 repo stars
- Updated July 28, 2026
- syncfusion/react-ui-components-skills
syncfusion-react-grid is a component-integration skill that guides developers implementing Syncfusion's React Data Grid with sorting, filtering, paging, editing, and data-binding patterns for enterprise React application
About
syncfusion-react-grid is an agent skill from syncfusion/react-ui-components-skills for building data-heavy React interfaces with Syncfusion's Grid component. It helps developers configure columns, data sources, sorting, filtering, paging, inline editing, selection, and export behaviors using Syncfusion React APIs and conventions. Teams reach for syncfusion-react-grid when admin dashboards, ERP views, or reporting screens need spreadsheet-grade tables without hand-rolling virtualized grids. The skill encodes Syncfusion-specific props, event handlers, and styling hooks so agents produce working grid implementations aligned with the library's documentation rather than generic HTML tables.
- syncfusion-react-grid
Syncfusion React Grid by the numbers
- 498 all-time installs (skills.sh)
- +61 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #831 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/react-ui-components-skills --skill syncfusion-react-gridAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 498 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/react-ui-components-skills ↗ |
How do you implement Syncfusion React Data Grid features?
Use syncfusion-react-grid for development tasks
Who is it for?
React developers building enterprise dashboards or admin tables who standardize on Syncfusion's commercial component suite.
Skip if: Teams using TanStack Table, AG Grid, or MUI DataGrid who do not license or install Syncfusion React packages.
When should I use this skill?
A developer is implementing, configuring, or debugging Syncfusion React Grid sorting, filtering, paging, editing, or data binding.
What you get
Configured Syncfusion Grid components with column defs, data adapters, filter/sort/paging handlers, and edit templates.
- configured data grid component
- column and filter definitions
Files
Syncfusion React Grid
The Syncfusion React Grid is a comprehensive, feature-rich component for displaying and manipulating tabular data. It provides extensive functionality for data binding, paging, sorting, filtering, grouping, editing, exporting, scrolling modes, row/column customization, and advanced state management to handle datasets of any size and complexity efficiently.
⚠️ Security & Trust Boundary
- The Grid skill does not perform any remote data access.
- All external API interaction is handled by a separate DataManager skill outside this skill’s trust boundary.
Table of Contents
- When to Use This Skill
- Component Overview
- Mandatory Rules for Inbuilt API
- Feature/Skill Navigation Guide
- Quick Start Example
When to Use This Skill
Use the Syncfusion React Grid when you need to:
- Display tabular data with rows and columns in a React application
- Handle large datasets efficiently with paging, virtual scrolling, or infinite scrolling
- Enable sorting, filtering, and searching for single or multiple columns
- Edit data inline with multiple edit modes (Inline, Batch, Dialog)
- Export data to Excel or PDF formats with customization
- Group and aggregate data with summaries and calculations
- Customize rows and cells with templates and styling
- Manage complex column configurations (frozen columns, spanning, reordering, resizing)
- Optimize performance when rendering thousands of records
- Persist grid state for user preferences and configuration
- Provide responsive data views for different screen sizes
Component Overview
The Grid component provides:
- Data Binding: Support for local arrays, remote data sources, and DataManager integration
- Paging: Client-side pagination with customizable page size and navigation
- Sorting: Single and multi-column sorting with custom comparers
- Filtering: Multiple filter modes (Filter Bar, Filter Menu, Excel-like filter)
- Searching: Global search across columns
- Grouping: Group data by columns with aggregates and lazy loading
- Selection: Row, cell, and column selection with checkbox support
- Editing: Inline, Batch, and Dialog modes with validation and templates
- Aggregates: Sum, Average, Count, Min, Max with footer, group, and caption display
- Exporting: Excel and PDF export with templates and server-side support
- Scrolling: Standard, Virtual, and Infinite scrolling modes
- Row Customization: Templates, detail views, drag-drop, pinning, spanning
- Column Customization: Templates, resizing, reordering, freezing, spanning
- Performance: Optimized rendering for large datasets
- Responsive Design: Adaptive layouts for different screen sizes
- Module System: Feature-based modules to reduce bundle size
Mandatory Rules for Inbuilt API
CRITICAL: Follow these rules when using Grid's inbuilt API (137+ methods, 65+ events, 95+ properties):
Rule 1: Module Injection Required for Feature Methods
Methods only work if their module is injected. No error thrown if module missing.
| Method | Required Module | Example |
|---|---|---|
goToPage() | Page | <Inject services={[Page]} /> |
sortColumn() | Sort | <Inject services={[Sort]} /> |
filterByColumn() | Filter | <Inject services={[Filter]} /> |
addRecord(), deleteRecord() | Edit | <Inject services={[Edit]} /> |
Rule 2: Properties vs Methods - Know the Difference
- Properties: Set via JSX props (
<GridComponent allowPaging={true} />) - Methods: Call via ref (
gridRef.current.goToPage(2)) - Can't set properties via methods or call methods via props
---
📄 Full Properties API Reference: references/properties-configuration.md 📄 Full Methods API Reference: references/programmatic-api.md 📄 Full Events API Reference: references/events-catalog.md 📄 Backend Integration: references/adaptors.md
---
Feature Navigation Guide
Getting Started & Setup
📄 Read: references/getting-started.md
- Installation and package setup
- Basic component initialization
- CSS imports and theme configuration
- Simple data binding example
- Module injection
Data Management
📄 Read: references/data-binding.md
- Local data binding with arrays
- Remote data with DataManager
- Loading indicators (Spinner, Shimmer)
- Data source configuration
Column Configuration
📄 Read: references/columns.md
- Column definition and properties
- Column types (string, number, date, boolean, checkbox)
- Column width and auto-fit
- Column templates and custom rendering
- Column features (spanning, reordering, resizing, freezing)
Aggregation & Summaries
📄 Read: references/aggregates.md
- Footer aggregates (Sum, Avg, Count, Min, Max, etc.)
- Group footer aggregates
- Group caption aggregates
- Custom aggregate functions
- Reactive aggregate updates
Navigation & Pagination
📄 Read: references/paging.md
- Enable and configure paging
- Page size and navigation
- Page change events
- Customizing pager UI
- Query string integration
Sorting Data
📄 Read: references/sorting.md
- Single and multi-column sorting
- Sort direction control
- Initial sort configuration
- Custom comparers
- Prevent sorting for specific columns
Filtering
Start here: 📄 Read references/filter-setup.md — Enable filtering, choose filter type (FilterBar, Menu, Excel, CheckBox)
Choose your type:
- 📄 Read references/filter-types.md — All 3 filter types: FilterBar (inline text), Menu (operators), Excel (checkboxes)
- 📄 Read references/filter-operators.md — All 21+ operators, syntax, wildcards, AND/OR logic
Searching
📄 Read: references/searching.md
- Grid search functionality
- Global search across columns
- Search with filter integration
- Case-sensitive search options
Grouping Data
📄 Read: references/grouping.md
- Enable grouping by columns
- Group by multiple columns
- Caption templates
- Group footer aggregates
- Lazy-load grouping for performance
Selection
📄 Read: references/selection.md
- Row, cell, column selection modes
- Checkbox selection
- Multiple selection handling
- Selection events and methods
- Programmatic selection
Editing
📄 Read: references/editing.md
- Enable editing (allowEditing, allowAdding, allowDeleting)
- Edit modes: Inline, Batch, Dialog
- Edit triggers (double-click, toolbar, keyboard)
- Custom edit templates
- Validation rules and error handling
- Primary key configuration
⚠️ isPrimaryKey={true} is required on the key column — editing silently fails without it.Row Features
📄 Read: references/row.md
- Row templates
- Detail templates (expand/collapse)
- Row drag and drop
- Row pinning
- Row spanning
- Row selection and events
Exporting
📄 Read: references/excel-export.md
- Basic Excel export functionality
- Export options and configuration
- Exporting with column templates
- Server-side export
- Formatting and styling in exports
📄 Read: references/pdf-export.md
- PDF export setup
- Headers and footers
- Export options and customization
- Exporting with templates
- Server-side PDF export
- Page orientation and sizing
Scrolling Modes
📄 Read: references/scrolling.md
- Standard scrolling
- Virtual scrolling (performance optimization)
- Infinite scrolling
- Scrollbar customization
- Height and width configuration
⚠️heightis required — scroll is silently disabled without it. Do NOT combine withallowPagingin virtual scrolling and infinite scrolling.
| Rows | Mode | Key Config |
|---|---|---|
| < 1,000 | allowPaging | Page module |
| 1K – 100K | enableVirtualization + height | VirtualScroll, no paging |
| Continuous | enableInfiniteScrolling + height | InfiniteScroll |
| 100K+ grouped | enableVirtualization + lazyLoadGrouping | VirtualScroll, Group |
Freezing & Pinning
📄 Read: references/frozen.md
- Freeze columns
- Freeze rows
- Freeze headers
- Row pinning
- Frozen column behavior
Toolbar
📄 Read: references/toolbar.md
- Built-in toolbar items
- Custom toolbar buttons
- Toolbar icons and events
- Edit toolbar integration
- Toolbar item templates
Context Menu
📄 Read: references/context-menu.md
- Built-in context menu items
- Custom context menus
- Context menu configuration
- Context menu events
Cell Operations
📄 Read: references/cell.md
- Cell editing and selection
- Cell templates
- Cell value formatting
- Cell ranges and operations
- Cell focus and navigation
📄 Read: references/clipboard.md
- Copy and paste functionality
- Clipboard events
- Custom clipboard actions
- Copy column headers
- Paste from external sources
Printing
📄 Read: references/print.md
- Print grid
- Print templates
- Print customization
- Print specific rows/columns
- Print orientation and sizing
Styling & Appearance
📄 Read: references/style-and-appearance.md
- CSS customization
- Themes (Material, Bootstrap, Fabric)
- Dark mode support
- Inline styling and classes
- Custom color schemes
- Component size modes
⚠️rowDataBoundandqueryCellInfofire on every render — no API calls inside them.
❌ Never add both a columntemplateand arowDataBoundhandler targeting the same field — this produces duplicate, conflicting styling logic.
Hierarchy & Nested Data
📄 Read: references/hierarchy-grid.md
- Parent-child data structures
- Child grid
- Expand and collapse behavior
- Nested grid templates
- Detail row templates
- Hierarchical data configuration
Adaptive Mode
📄 Read: references/adaptive.md
- enableAdaptiveUI: Render filter, sort, column chooser, and edit dialogs in full-screen mode for better mobile usability. Apply
e-biggerclass to parent element. - Vertical row rendering: Set
rowRenderingMode="Vertical"to display row elements vertically instead of horizontally (default is Horizontal). - Vertical rendering features: Supports paging, sorting, filtering, selection, dialog editing, aggregates, infinite scroll, and toolbar options.
- rowRenderingMode note: Column Menu (grouping, sorting, autofit, filter, column chooser) only works in Horizontal mode.
- AdaptiveUIMode property: Use
"Mobile"to render adaptive layout only on mobile screens, or"Both"(default) for both mobile and desktop. - Mobile screen optimization: Render adaptive dialogs and vertical layouts automatically when enableAdaptiveUI is enabled.
Configuration Management
📄 Read: references/global-local.md
- Global grid settings
- Local column configuration
- Configuration precedence
- Configuration merging
📄 Read: references/state-management.md
- Persist grid state
- Save and restore grid state manually
- Restore grid to initial state
- Prevent specific properties from persisting
- Persist column templates and header text
⚠️ Rule: enablePersistence={true} must be enable when use state management in grid.
Module System & Architecture
📄 Read: references/modules.md
- Grid module architecture and feature-based modules
- 30+ available modules and their dependencies
- Module injection patterns
- Feature-module mapping
- Bundle optimization and lazy loading
- Conditional module loading
- Every feature in the Syncfusion React Grid requires explicit module injection via
<Inject services={[...]} />. Without injection, the feature is silently ignored — no error is thrown.
⚠️ Rule: <Inject services={[...]} /> must be the last child inside <GridComponent>.
Data Connectivity & Adaptors
📄 Read: references/adaptors.md
- 7 adaptor types for backend integration
- UrlAdaptor, ODataV4Adaptor, WebApiAdaptor, GraphQLAdaptor
- Custom adaptors and RemoteSaveAdaptor
- Backend configuration examples (C#, Node.js)
- Request/response format specifications
- Error handling and adaptor comparison
Performance Optimization
📄 Read: references/performance.md
- Virtual scrolling for 10,000+ records
- Infinite scrolling and progressive loading
- Memory management and cleanup strategies
- Bundle size optimization
- Event debouncing and throttling
- Performance benchmarking and monitoring
Accessibility & Compliance
📄 Read: references/accessibility.md
- WCAG 2.2 and Section 508 compliance
- WAI-ARIA implementation and screen readers
- Keyboard navigation (Tab, arrows, Enter, Escape)
- Color contrast and focus management
- Accessibility testing tools (axe, NVDA, JAWS)
- Semantic HTML practices
Data Validation
📄 Read: references/validation.md
- Built-in validators (required, min, max, pattern)
- Custom validation functions
- Async validation with server checks
- Validation events and error display
- Server-side validation with ASP.NET
- Validation rules by column type
Command Column & Row Actions
📄 Read: references/command-column.md
- Built-in commands (Edit, Delete, Save, Cancel)
- Custom command buttons
- Role-based and status-based commands
- Command click events
- Conditional command visibility
- CSS styling and icons
Localization & Internationalization
📄 Read: references/localization.md
- Multi-language support (60+ languages)
- Locale setup and culture configuration
- Number, date, and currency formatting
- RTL support (Arabic, Hebrew, Farsi)
- Custom localization and translation
- Language switcher implementation
Responsive Design & Mobile
📄 Read: references/responsive-design.md
- Adaptive UI for mobile/tablet/desktop
- Responsive media queries and breakpoints
- Column visibility hiding rules
- Touch interactions (swipe, long-press)
- Mobile optimization strategies
- Device-specific styling patterns
API Reference & Properties
📄 Read: references/properties-configuration.md
- All 95+ configurable properties organized by category
- Quick reference table for quick lookup
- When to use each property
- Module requirements per property
Programmatic Control (Methods)
📄 Read: references/programmatic-api.md
- All 137+ Grid methods by category (data, row, column, sort, filter, group, page, edit, export)
- Method signatures and parameters
- Return values and usage patterns
- Module requirements per method
Event Communication (Events)
📄 Read: references/events-catalog.md
- All 65+ Grid events with timing and use cases
- Wire
actionBeginto cancel or mutate before an action (args.cancel = true,args.data.field = value) - Wire
actionCompleteto react after (API call, toast, refresh, toolbar restore) - Must wire
actionFailurefor error handling - Use
args.requestTypeto identify the action
Advanced Patterns & Real-World Scenarios
📄 Real-Time Updates: references/real-time-updates.md
- Auto-refresh, WebSocket, and SignalR integration patterns
- Push vs. pull data strategies
- Connection management and reconnection logic
📄 Performance Monitoring: references/performance-monitoring.md
- Measure grid operation times
- Identify rendering bottlenecks
- Optimization checklist
- Memory usage tracking
📄 Testing Grids: references/testing-grids.md
- Unit testing with Jest and React Testing Library
- Integration testing scenarios
- Snapshot testing
- Best practices for grid testing
Quick Start Example
import { ColumnDirective, ColumnsDirective, GridComponent, Inject, Page, Sort, Filter } from '@syncfusion/ej2-react-grids';
import React from 'react';
import '../styles/App.css';
const gridRef = useRef<GridComponent | null>(null);
// Sample data
const data = [
{
OrderID: 10248,
CustomerID: 'VINET',
EmployeeID: 5,
OrderDate: new Date(1996, 6, 4)
},
{
OrderID: 10249,
CustomerID: 'TOMSP',
EmployeeID: 6,
OrderDate: new Date(1996, 6, 5)
},
];
function App() {
return (
<GridComponent
ref={gridRef}
dataSource={data}
allowPaging={true}
allowSorting={true}
allowFiltering={true}
pageSettings={{ pageSize: 10 }}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' textAlign='Right' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='OrderDate' headerText='Order Date' width='130' type='date' format='yMd' />
</ColumnsDirective>
<Inject services={[Page, Sort, Filter]} />
</GridComponent>
);
}
export default App;Accessibility (WCAG 2.2 & Section 508) in React Grid
Table of Contents
- Overview
- Keyboard Navigation
- WCAG 2.2 Compliance
- WAI-ARIA Implementation
- Screen Reader Support
- Color Contrast
- Focus Management
- Accessibility Testing
Overview
Syncfusion React Grid is built with accessibility as a core principle, supporting:
- WCAG 2.2 (Web Content Accessibility Guidelines Level AA)
- Section 508 (U.S. accessibility standards)
- WAI-ARIA 1.2 (Web Accessibility Initiative - Accessible Rich Internet Applications)
- Keyboard-only navigation
- Screen reader compatibility
---
Keyboard Navigation
Grid Navigation Keys
| Key | Action |
|---|---|
| Tab | Move to next cell/control |
| Shift + Tab | Move to previous cell/control |
| Arrow Keys | Navigate within rows/columns |
| Ctrl + Home | Go to first cell |
| Ctrl + End | Go to last cell |
| Page Up | Previous page (paging enabled) |
| Page Down | Next page (paging enabled) |
| Space | Select row/check checkbox |
| Enter | Edit cell / Confirm action |
| Escape | Cancel edit / Close dialog |
| Ctrl + A | Select all |
| Ctrl + C | Copy (with clipboard enabled) |
| Ctrl + X | Cut (with clipboard enabled) |
| Ctrl + V | Paste (with clipboard enabled) |
Enable Keyboard Navigation
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Page } from '@syncfusion/ej2-react-grids';
<GridComponent
dataSource={data}
allowKeyboard={true} // Enable keyboard navigation (default: true)
allowPaging={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
</ColumnsDirective>
<Inject services={[Page]} />
</GridComponent>Keyboard Event Handling
const gridRef = useRef(null);
const handleKeyDown = (event) => {
if (event.key === 'F2') {
// Custom action on F2
const selectedRows = gridRef.current.getSelectedRows();
if (selectedRows.length > 0) {
gridRef.current.startEdit(selectedRows[0]);
}
}
};
return (
<div onKeyDown={handleKeyDown}>
<GridComponent ref={gridRef} dataSource={data}>
{/* columns */}
</GridComponent>
</div>
);---
WCAG 2.2 Compliance
Perceivable
Information must be presentable to users:
// ✅ Provide text alternatives for images
<ColumnDirective
field='Photo'
headerText='Employee Photo'
template={(props) => (
<img
src={props.Photo}
alt={`Photo of ${props.FirstName} ${props.LastName}`}
style={{ width: '32px', height: '32px' }}
/>
)}
/>
// ✅ Use semantic HTML
<GridComponent
dataSource={data}
ariaLabel='Employee data grid'
>
{/* columns */}
</GridComponent>
// ✅ Sufficient contrast ratios (4.5:1 for normal text)
<div style={{ color: '#000000', backgroundColor: '#FFFFFF' }}>
High contrast text
</div>Operable
Users must be able to navigate and interact:
// ✅ Keyboard accessible
<GridComponent
dataSource={data}
allowKeyboard={true}
allowSelection={true}
>
{/* columns */}
</GridComponent>
// ✅ Provide skip links
function App() {
return (
<>
<a href='#main-grid' className='skip-link'>Skip to grid</a>
<GridComponent id='main-grid' dataSource={data}>
{/* columns */}
</GridComponent>
</>
);
}Understandable
Content must be clear and predictable:
// ✅ Use clear labels
<ColumnDirective
field='OrderDate'
headerText='Order Date'
type='date'
format='MM/dd/yyyy'
/>
// ✅ Predictable behavior
const handleActionBegin = (args) => {
if (args.requestType === 'save') {
console.log('Data saved'); // Confirm action
}
};
<GridComponent actionBegin={handleActionBegin}>
{/* columns */}
</GridComponent>
// ✅ Clear instructions
<div>
<label htmlFor='grid-filter'>Filter by customer:</label>
<GridComponent
id='grid-filter'
dataSource={data}
allowFiltering={true}
>
{/* columns */}
</GridComponent>
</div>---
WAI-ARIA Implementation
ARIA Attributes
Syncfusion Grid automatically adds ARIA attributes:
// Automatically added by Grid:
// role='grid'
// role='row' for each row
// role='gridcell' for each cell
// role='button' for headers (clickable)
// aria-selected='true/false'
// aria-sort='ascending/descending/none'
// aria-expanded='true/false' (for detail rows)
// aria-disabled='true/false'
// aria-readonly='true/false'
<GridComponent
dataSource={data}
allowSorting={true}
detailTemplate={detailTemplate}
>
{/* columns */}
</GridComponent>Custom ARIA Labels
<GridComponent
dataSource={data}
ariaLabel='Employee database'
rowTemplate={(props) => (
<tr aria-label={`Employee ${props.FirstName} ${props.LastName}`}>
<td>{props.FirstName}</td>
<td>{props.LastName}</td>
</tr>
)}
>
{/* columns */}
</GridComponent>ARIA Live Regions
Announce dynamic content changes:
import React, { useRef, useState } from 'react';
function GridWithAnnouncements() {
const gridRef = useRef(null);
const [announcement, setAnnouncement] = useState('');
const handleRowAction = (action) => {
setAnnouncement(`${action} performed`);
// Clear announcement after screen reader reads it
setTimeout(() => setAnnouncement(''), 1000);
};
return (
<div>
<div
role='status'
aria-live='polite'
aria-atomic='true'
className='sr-only'
>
{announcement}
</div>
<GridComponent
ref={gridRef}
dataSource={data}
recordDoubleClick={() => handleRowAction('Row opened')}
>
{/* columns */}
</GridComponent>
</div>
);
}---
Screen Reader Support
Test with NVDA/JAWS
1. Install NVDA (free) or JAWS 2. Enable screen reader 3. Tab through grid 4. Verify announcements
Announce Selection
<GridComponent
dataSource={data}
selectionSettings={{ type: 'Multiple', mode: 'Row' }}
rowSelected={(args) => {
const announcement = `Row ${args.data.OrderID} selected`;
announceToScreenReader(announcement);
}}
>
{/* columns */}
</GridComponent>
const announceToScreenReader = (message) => {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
};Describe Complex Cells
// Add descriptions for complex content
<ColumnDirective
field='Status'
headerText='Status'
template={(props) => (
<div>
<span
style={{
backgroundColor: props.Status === 'Active' ? 'green' : 'red',
color: 'white',
padding: '4px 8px',
borderRadius: '4px'
}}
aria-label={`Status: ${props.Status}`}
>
{props.Status}
</span>
</div>
)}
/>Header Descriptions
<ColumnDirective
field='Freight'
headerText='Freight Cost'
template={() => (
<div>
<span>Freight Cost (USD)</span>
<span className='sr-only'>Shipping cost in US dollars</span>
</div>
)}
/>---
Color Contrast
WCAG AA Requirements
- Normal text: 4.5:1 ratio
- Large text (18+ or 14+ bold): 3:1 ratio
Contrast Check
// ✅ Good contrast (Black on White = 21:1)
<div style={{ color: '#000000', backgroundColor: '#FFFFFF' }}>
Text with excellent contrast
</div>
// ✅ Good for large text (3:1)
<div style={{ color: '#0066CC', backgroundColor: '#FFFFFF', fontSize: '18px', fontWeight: 'bold' }}>
Large blue text
</div>
// ❌ Bad contrast (Gray on White = 1.5:1)
<div style={{ color: '#CCCCCC', backgroundColor: '#FFFFFF' }}>
Poor contrast text (fail)
</div>Apply to Grid
// Custom theme with good contrast
const gridStyles = `
.e-grid .e-gridcontent td {
color: #000000; /* High contrast */
background-color: #FFFFFF;
}
.e-grid .e-headercell {
color: #FFFFFF;
background-color: #0066CC; /* 4.5:1 ratio */
}
.e-grid .e-selectionbackground {
background-color: #0066CC;
color: #FFFFFF;
}
`;
<style>{gridStyles}</style>
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>---
Focus Management
Visible Focus Indicator
const focusStyles = `
.e-grid .e-gridcontent td:focus,
.e-grid .e-headercell:focus {
outline: 3px solid #4A90E2;
outline-offset: 2px;
}
`;
<style>{focusStyles}</style>
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>Restore Focus on Dialog Close
import React, { useRef } from 'react';
function GridWithDialog() {
const gridRef = useRef(null);
const previousFocusRef = useRef(null);
const handleEditStart = () => {
previousFocusRef.current = document.activeElement;
};
const handleEditComplete = () => {
// Restore focus to previously focused element
if (previousFocusRef.current) {
previousFocusRef.current.focus();
}
};
return (
<GridComponent
ref={gridRef}
dataSource={data}
editSettings={{ mode: 'Dialog', allowEditing: true }}
actionBegin={(args) => {
if (args.requestType === 'beginEdit') handleEditStart();
}}
actionComplete={(args) => {
if (args.requestType === 'save') handleEditComplete();
}}
>
{/* columns */}
</GridComponent>
);
}
export default GridWithDialog;---
Accessibility Testing
Automated Testing with axe DevTools
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('grid should be accessible', async () => {
const { container } = render(<GridApp />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});Manual Testing Checklist
- [ ] All features keyboard accessible
- [ ] Tab order is logical
- [ ] Focus indicators visible
- [ ] No keyboard traps
- [ ] Color not only differentiator
- [ ] Contrast ratios met (4.5:1)
- [ ] Headings properly structured
- [ ] Labels associated with inputs
- [ ] Form validation messages clear
- [ ] Dynamic content announced
- [ ] Images have alt text
- [ ] Links have descriptive text
- [ ] No auto-playing audio/video
- [ ] Resizable text works
- [ ] Works with screen readers
- [ ] Responsive on mobile with zoom
Browser Extensions for Testing
- axe DevTools: Accessibility checker
- WAVE: Web accessibility tool
- Lighthouse: Built-in Chrome audit
- NVDA: Free screen reader (Windows)
- JAWS: Professional screen reader
Test Report Template
Grid Accessibility Audit - [Date]
✅ Keyboard Navigation
- All functions accessible
- Logical tab order
- No keyboard traps
✅ WCAG 2.2 Compliance
- Level A: PASSED
- Level AA: PASSED
✅ Screen Reader Support
- NVDA: PASSED
- JAWS: PASSED
✅ Color & Contrast
- Text: 4.5:1
- UI Components: 3:1
Notes:
- [Any issues found]---
Best Practices Summary
1. Always enable keyboard navigation
allowKeyboard={true}2. Use semantic HTML
<ColumnDirective ariaLabel='...' />3. Maintain color contrast
- 4.5:1 for normal text
- 3:1 for large text
4. Provide clear labels and instructions
<label htmlFor='grid-id'>Instructions here</label>5. Test with assistive technology
- Use NVDA or JAWS
- Use axe automation
6. Implement focus management
- Show focus indicator
- Maintain logical focus order
7. Use ARIA appropriately
- Let Grid handle automatic ARIA
- Add custom ARIA when needed
8. Make dynamic changes announcements
- Use aria-live regions
- Announce data updates
9. Ensure print-friendly
@media print {
.grid { /* print styles */ }
}10. Document accessibility features
- Include in user guide
- Provide keyboard shortcuts
Adaptive Mode in React Grid
Adaptive Dialogs
Enable enableAdaptiveUI to render filter, sort, and edit dialogs in full-screen mode. Apply e-bigger class to parent element.
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Filter, Sort, Edit, Toolbar, Page } from '@syncfusion/ej2-react-grids';
<div className="e-adaptive-demo e-bigger">
<GridComponent
dataSource={data}
enableAdaptiveUI={true}
allowFiltering={true}
allowSorting={true}
allowPaging={true}
editSettings={{ allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Dialog' }}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='150' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer Name' width='160' />
<ColumnDirective field='Freight' headerText='Freight' width='150' format='C2' />
</ColumnsDirective>
<Inject services={[Filter, Sort, Edit, Toolbar, Page]} />
</GridComponent>
</div>Vertical Row Rendering
Set rowRenderingMode="Vertical" to display rows vertically (default is Horizontal). Requires enableAdaptiveUI={true}.
Supported Features: Paging, Sorting, Filtering, Selection, Dialog Editing, Aggregates, Infinite Scroll, Toolbar
Note: Column Menu only works in Horizontal mode.
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Filter, Sort, Edit, Toolbar, Page } from '@syncfusion/ej2-react-grids';
<GridComponent
dataSource={data}
enableAdaptiveUI={true}
rowRenderingMode="Vertical"
allowFiltering={true}
allowSorting={true}
allowPaging={true}
editSettings={{ allowEditing: true, mode: 'Dialog' }}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='150' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer Name' width='160' />
<ColumnDirective field='Freight' headerText='Freight' width='150' format='C2' />
</ColumnsDirective>
<Inject services={[Filter, Sort, Edit, Toolbar, Page]} />
</GridComponent>Adaptive UI Modes
Use adaptiveUIMode property:
'Both'(default): Render on mobile and desktop'Mobile': Render only on mobile screens
<GridComponent
dataSource={data}
enableAdaptiveUI={true}
adaptiveUIMode="Mobile"
allowFiltering={true}
allowSorting={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='150' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer Name' width='160' />
</ColumnsDirective>
<Inject services={[Filter, Sort]} />
</GridComponent>Data Adaptors in React Grid
Table of Contents
- Overview
- URL Adaptor
- ODataV4 Adaptor
- WebAPI Adaptor
- GraphQL Adaptor
- Custom Adaptor
- RemoteSave Adaptor
- Adaptor Comparison
- Error Handling
- Best Practices
Overview
Data adaptors provide an interface between the Grid component and various backend services. They handle data fetching, filtering, sorting, grouping, and CRUD operations on the server side.
Adaptors are configured through the DataManager component using the adaptor property.
URL Adaptor
Simplest adaptor for REST APIs with standard HTTP GET/POST requests.
Setup
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { GridComponent } from '@syncfusion/ej2-react-grids';
const data = new DataManager({
url: 'url,
adaptor: new UrlAdaptor()
});
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>Server Response Format
{
"d": [
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
],
"__count": 830
}Or with array response:
[
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
]Example Implementation
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Page, Sort, Filter } from '@syncfusion/ej2-react-grids';
function App() {
const data = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
return (
<GridComponent dataSource={data} allowPaging={true} allowSorting={true} allowFiltering={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' />
</ColumnsDirective>
<Inject services={[Page, Sort, Filter]} />
</GridComponent>
);
}
export default App;---
ODataV4 Adaptor
Specialized for OData v4 protocol. Provides advanced filtering, sorting, and server-side operations.
Setup
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>Benefits
- Advanced Filtering: Complex filter expressions
- Selective Loading: Only required columns via $select
- Server-Side Aggregates: Count, sum, average on server
- Relationship Navigation: Access related entities via $expand
Query Parameters
$select - Select specific columns
$filter - Complex filter expressions
$orderby - Sort by one or more fields
$skip - Skip N records (pagination)
$top - Return N records
$count - Include total count
$expand - Include related dataExample Queries
// Basic with paging
/Orders?$skip=0&$top=12&$count=true
// With filtering
/Orders?$filter=Freight gt 50 and ShipCity eq 'London'
// With sorting
/Orders?$orderby=OrderDate desc,CustomerID asc
// Selective columns
/Orders?$select=OrderID,CustomerID,Freight
// With relationships
/Orders?$expand=Customer,Employee
// Complex filter
/Orders?$filter=contains(tolower(CustomerID),'a') and Freight lt 100Complex OData Filters
import { Predicate } from '@syncfusion/ej2-data';
const predicate = new Predicate('Freight', 'greaterThan', 50);
predicate = predicate.and('ShipCity', 'equal', 'London');
predicate = predicate.or('CustomerID', 'startsWith', 'A');
const gridInstance = gridRef.current;
gridInstance.query = new Query().where(predicate);
gridInstance.refresh();Example with Expand
const data = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='Customer.CompanyName' headerText='Company' width='150' />
<ColumnDirective field='Employee.FirstName' headerText='Employee' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' />
</ColumnsDirective>
<Inject services={[Page, Sort, Filter]} />
</GridComponent>---
WebAPI Adaptor
For ASP.NET Web API services using RESTful conventions.
Setup
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
crossDomain: true
});
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>ASP.NET Web API Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet]
public IActionResult GetOrders([FromQuery] DataManagerRequest dm)
{
var orders = GetAllOrders(); // Your data source
if (dm.Search != null && dm.Search.Count > 0)
{
orders = orders.Where(o => o.CustomerID.Contains(dm.Search[0].Key)).ToList();
}
if (dm.Sorted != null && dm.Sorted.Count > 0)
{
foreach (var sort in dm.Sorted)
{
if (sort.Direction == "Ascending")
orders = orders.OrderBy(x => x.GetType().GetProperty(sort.Name)?.GetValue(x)).ToList();
else
orders = orders.OrderByDescending(x => x.GetType().GetProperty(sort.Name)?.GetValue(x)).ToList();
}
}
var count = orders.Count();
if (dm.Skip > 0)
orders = orders.Skip(dm.Skip).ToList();
if (dm.Take > 0)
orders = orders.Take(dm.Take).ToList();
return Ok(new { result = orders, count = count });
}
[HttpPost]
public IActionResult Create([FromBody] Order value)
{
// Insert logic
return Ok(value);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] Order value)
{
// Update logic
return Ok(value);
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
// Delete logic
return Ok(id);
}
}React Grid With CRUD
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
import { GridComponent, Inject, Edit, Toolbar } from '@syncfusion/ej2-react-grids';
const data = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
insertUrl: 'url',
updateUrl: 'url',
removeUrl: 'url',
batchUrl: 'url/batch'
});
<GridComponent
dataSource={data}
editSettings={{ allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Dialog' }}
toolbar={['Add', 'Edit', 'Delete', 'Update', 'Cancel']}
>
{/* columns */}
<Inject services={[Edit, Toolbar]} />
</GridComponent>---
GraphQL Adaptor
For GraphQL endpoints returning complex nested queries.
Setup
import { DataManager, GraphQLAdaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor(),
query: `query {
orders(skip: 0, take: 12) {
items {
orderId
customerId
freight
}
totalCount
}
}`
});
<GridComponent dataSource={data}>
{/* columns */}
</GridComponent>GraphQL Query with Variables
const data = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor(),
query: `query getOrders($skip: Int!, $take: Int!, $filter: String) {
orders(skip: $skip, take: $take, filter: $filter) {
items {
orderId
customerId
orderDate
freight
}
totalCount
}
}`
});GraphQL API Example (Node.js)
const express = require('express');
const { graphql, buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
orders(skip: Int, take: Int, filter: String): OrderResult
}
type Order {
orderId: Int
customerId: String
orderDate: String
freight: Float
}
type OrderResult {
items: [Order]
totalCount: Int
}
`);
const root = {
orders: ({ skip = 0, take = 12, filter }) => {
let orders = getAllOrders(); // Your data
if (filter) {
orders = orders.filter(o => o.customerId.includes(filter));
}
return {
items: orders.slice(skip, skip + take),
totalCount: orders.length
};
}
};
app.post('/graphql', express.json(), async (req, res) => {
const result = await graphql(schema, req.body.query, root);
res.json(result);
});---
Custom Adaptor
For non-standard backend implementations.
Creating a Custom Adaptor
import { DataManager, Adaptor, Query } from '@syncfusion/ej2-data';
class CustomAdaptor extends Adaptor {
processQuery(dm, query, hierarchyIndex) {
// Custom query processing
const req = this.baseUrl;
const filter = query.params.filter || '';
const sort = query.params.sort || '';
const page = query.params.pageIndex || 1;
const pageSize = query.params.pageSize || 12;
return {
type: 'GET',
url: `${req}?filter=${filter}&sort=${sort}&page=${page}&pageSize=${pageSize}`
};
}
processResponse(data, dm, query, xhr, request, key) {
// Process response to expected format
if (data.result) {
return { result: data.result, count: data.totalCount };
}
return data;
}
insert(dm, value, tableName, key) {
// Custom insert logic
return {
type: 'POST',
url: dm.insertUrl || dm.baseUrl,
data: JSON.stringify(value),
contentType: 'application/json'
};
}
update(dm, keyField, value, tableName, key) {
// Custom update logic
return {
type: 'PUT',
url: `${dm.updateUrl || dm.baseUrl}/${value[keyField]}`,
data: JSON.stringify(value),
contentType: 'application/json'
};
}
remove(dm, keyField, value, tableName, key) {
// Custom delete logic
return {
type: 'DELETE',
url: `${dm.removeUrl || dm.baseUrl}/${value[keyField]}`
};
}
}
// Usage
const data = new DataManager({
url: 'url,
adaptor: new CustomAdaptor()
});Custom Adaptor with Special Headers
class AuthenticatedAdaptor extends Adaptor {
processRequest(dm, request, state) {
request.headers = {
...request.headers,
'Authorization': `send_token`,
'X-Custom-Header': 'CustomValue'
};
return request;
}
processResponse(data, dm, query, xhr, request, key) {
if (xhr.status === 401) {
// Handle unauthorized
redirectToLogin();
}
return data;
}
}---
RemoteSave Adaptor
Specialized for batch CRUD operations (multiple insert/update/delete in single request).
Setup
import { DataManager, RemoteSaveAdaptor } from '@syncfusion/ej2-data';
import { GridComponent, Inject, Edit, Toolbar } from '@syncfusion/ej2-react-grids';
const data = new DataManager({
url: 'url',
adaptor: new RemoteSaveAdaptor(),
batchUrl: 'url/batch'
});
<GridComponent
dataSource={data}
editSettings={{ allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' }}
toolbar={['Add', 'Edit', 'Delete', 'Update', 'Cancel']}
>
{/* columns */}
<Inject services={[Edit, Toolbar]} />
</GridComponent>Batch Request Format
{
"changed": [
{ "OrderID": 10248, "CustomerID": "VINET_UPDATED" }
],
"added": [
{ "CustomerID": "NEWCUST", "Freight": 25.5 }
],
"deleted": [
{ "OrderID": 10249 }
]
}ASP.NET Backend
[HttpPost("batch")]
public IActionResult UpdateBatch([FromBody] BatchRequest request)
{
if (request.Changed != null)
foreach (var order in request.Changed)
UpdateOrder(order);
if (request.Added != null)
foreach (var order in request.Added)
InsertOrder(order);
if (request.Deleted != null)
foreach (var order in request.Deleted)
DeleteOrder(order.OrderID);
return Ok();
}
public class BatchRequest
{
public List<Order> Changed { get; set; }
public List<Order> Added { get; set; }
public List<Order> Deleted { get; set; }
}---
Adaptor Comparison
| Feature | URL | OData | WebAPI | GraphQL | Custom |
|---|---|---|---|---|---|
| Easy Setup | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⚠️ Complex |
| Filtering | ⭐⭐⭐ | ⭐⭐⭐ | Good | Complex | Custom |
| Sorting | ⭐⭐⭐ | ⭐⭐⭐ | Good | Custom | Custom |
| Pagination | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | Custom | Custom |
| Aggregates | ⭐⭐⭐ | ⭐⭐⭐ | ⚠️ | Custom | Custom |
| Relationships | ❌ | ⭐⭐⭐ | Basic | ⭐⭐⭐ | Custom |
| CRUD | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | Custom | Custom |
| Performance | Good | Good | Good | Variable | Variable |
---
Error Handling
Global Error Handler
const data = new DataManager({
url: 'url,
adaptor: new UrlAdaptor()
});
<GridComponent
dataSource={data}
actionFailure={(error) => handleError(error)}
>
{/* columns */}
</GridComponent>
const handleError = (error) => {
console.error('Grid Error:', error);
if (error.error?.status === 401) {
// Handle unauthorized
redirectToLogin();
} else if (error.error?.status === 500) {
// Handle server error
showErrorNotification('Server error occurred');
} else {
showErrorNotification('An error occurred');
}
};Request/Response Interceptor
class InterceptorAdaptor extends Adaptor {
beforeSend(request) {
console.log('Request:', request);
// Add auth headers, etc.
return request;
}
processResponse(data, dm, query, xhr, request, key) {
console.log('Response:', data);
if (data.error) {
throw new Error(data.error.message);
}
return data;
}
}Retry Logic for Failed Requests
const createDataManagerWithRetry = (url) => {
const data = new DataManager({
url: url,
adaptor: new UrlAdaptor()
});
let retryCount = 0;
const maxRetries = 3;
data.onActionFailure = (error) => {
if (retryCount < maxRetries) {
retryCount++;
console.log(`Retry attempt ${retryCount}...`);
setTimeout(() => data.executeQuery(), 1000 * retryCount);
} else {
console.error('Max retries exceeded');
}
};
return data;
};---
Best Practices
1. Choose the right adaptor for your backend type 2. Implement server-side filtering for large datasets 3. Use pagination to limit data transfer 4. Handle errors gracefully with user-friendly messages 5. Add loading indicators during data fetching 6. Optimize queries - only fetch needed columns 7. Implement authentication for secure APIs 8. Cache data when appropriate 9. Test error scenarios thoroughly 10. Monitor performance with network inspector
Aggregates in React Grid
Table of Contents
- Overview
- Setup and Configuration
- Aggregate Types
- Footer Aggregates
- Group Aggregates
- Caption Aggregates
- Custom Aggregates
- Reactive Updates
Overview
Aggregates provide summary calculations (Sum, Average, Count, Min, Max) that can be displayed in footer rows, group footers, or group captions. This is useful for displaying total quantities, average values, and other summary statistics.
Setup and Configuration
Enable Aggregate Module
import { Inject, Aggregate } from '@syncfusion/ej2-react-grids';
<GridComponent>
<Inject services={[Aggregate]} />
</GridComponent>Using Directives
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
AggregatesDirective,
AggregateDirective,
AggregateColumnsDirective,
AggregateColumnDirective,
Inject,
Aggregate
} from '@syncfusion/ej2-react-grids';
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective field='Freight' headerText='Freight' format='C2' />
</ColumnsDirective>
<AggregatesDirective>
<AggregateDirective>
<AggregateColumnsDirective>
<AggregateColumnDirective field='Freight' type='Sum' />
</AggregateColumnsDirective>
</AggregateDirective>
</AggregatesDirective>
<Inject services={[Aggregate]} />
</GridComponent>Aggregate Types
Supported Aggregate Functions
| Type | Description | Example |
|---|---|---|
Sum | Total of all values | type='Sum' |
Average | Mean value | type='Average' |
Count | Number of records | type='Count' |
Min | Minimum value | type='Min' |
Max | Maximum value | type='Max' |
TrueCount | Count of true values | type='TrueCount' |
FalseCount | Count of false values | type='FalseCount' |
Footer Aggregates
Display summary values in grid footer:
const footerSum = (props) => {
return <span>Total: {props.Sum}</span>;
};
<AggregatesDirective>
<AggregateDirective>
<AggregateColumnsDirective>
<AggregateColumnDirective
field='Freight'
type='Sum'
format='C2'
footerTemplate={footerSum}
/>
<AggregateColumnDirective
field='Freight'
type='Average'
format='C2'
footerTemplate={(props) => <span>Average: ${props.Average}</span>}
/>
</AggregateColumnsDirective>
</AggregateDirective>
</AggregatesDirective>Multiple Aggregates in Footer
<AggregatesDirective>
<AggregateDirective>
<AggregateColumnsDirective>
<AggregateColumnDirective field='Freight' type='Sum' footerTemplate={`Sum: \${Sum}`} />
<AggregateColumnDirective field='Freight' type='Average' footerTemplate={`Avg: \${Average}`} />
<AggregateColumnDirective field='Freight' type='Max' footerTemplate={`Max: \${Max}`} />
<AggregateColumnDirective field='OrderID' type='Count' footerTemplate={`Count: \${Count}`} />
</AggregateColumnsDirective>
</AggregateDirective>
</AggregatesDirective>Group Aggregates
Display summary values for each group:
<AggregatesDirective>
<AggregateDirective>
<AggregateColumnsDirective>
<AggregateColumnDirective
field='Freight'
type='Sum'
format='C2'
groupFooterTemplate={`Group Total: \${Sum}`}
/>
</AggregateColumnsDirective>
</AggregateDirective>
</AggregatesDirective>Template prop determines where the aggregate appears in the group:
- groupFooterTemplate → rendered in the footer row of each group (below group rows)- groupCaptionTemplate → rendered in the caption row of each group (at the top of the group)- footerTemplate → rendered in the overall grid footer (not per group — do not use this for group summaries)Group Footer with Formatting
const groupFooterSum = (props) => {
return (
<div>
<strong>Group Sum: ${props.Sum}</strong>
</div>
);
};
<AggregateColumnDirective
field='Freight'
type='Sum'
format='C2'
groupFooterTemplate={groupFooterSum}
/>Caption Aggregates
Display summaries in group caption row:
<AggregatesDirective>
<AggregateDirective>
<AggregateColumnsDirective>
<AggregateColumnDirective
field='Freight'
type='Sum'
format='C2'
groupCaptionTemplate={`Total Freight: \${Sum}`}
/>
</AggregateColumnsDirective>
</AggregateDirective>
</AggregatesDirective>Custom Group Caption Format
const groupCaption = (props) => {
return (
<div>
<strong>{props.ShipCountry}</strong> - {props.Sum} items
</div>
);
};
<AggregateColumnDirective
field='OrderID'
type='Count'
groupCaptionTemplate={groupCaption}
/>Custom Aggregates
Create custom aggregate calculations:
const customAggregate = (args) => {
const total = args.reduce((acc, item) => acc + item.Freight, 0);
return (total / args.length) * 1.1; // 10% markup
};
<AggregateColumnDirective
field='Freight'
customAggregate={customAggregate}
footerTemplate={`Custom: \${customAggregate}`}
/>Complex Custom Aggregate
const calculateMedian = (field, records) => {
const values = records.map(r => r[field]).sort((a, b) => a - b);
const mid = Math.floor(values.length / 2);
return values.length % 2 !== 0
? values[mid]
: (values[mid - 1] + values[mid]) / 2;
};Reactive Updates
Update Aggregates on Data Change
import React, { useState } from 'react';
function App() {
const [gridData, setGridData] = useState(initialData);
const addRow = () => {
setGridData([
...gridData,
{ OrderID: 10251, Freight: 100, ShipCountry: 'France' }
]);
// Aggregates automatically recalculate
};
return (
<div>
<button onClick={addRow}>Add Row</button>
<GridComponent dataSource={gridData}>
<AggregatesDirective>
{/* aggregate definitions */}
</AggregatesDirective>
</GridComponent>
</div>
);
}Using Aggregate Configuration Object
const aggregates = [
{
columns: [
{
field: 'Freight',
type: 'Sum',
format: 'C2',
footerTemplate: 'Sum: ${Sum}'
},
{
field: 'Freight',
type: 'Average',
format: 'C2',
footerTemplate: 'Avg: ${Average}'
}
]
}
];
<GridComponent dataSource={data} aggregates={aggregates}>
{/* columns and other configs */}
</GridComponent>Cell Features in React Grid
Table of Contents
Overview
Cell-level operations provide granular control over individual cell display, editing, and interaction.
Cell Selection
Enable Cell Selection
import { GridComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-grids';
const selectionSettings = {
type: 'Multiple',
mode: 'Cell'
};
<GridComponent
dataSource={data}
selectionSettings={selectionSettings}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
</ColumnsDirective>
</GridComponent>Get Selected Cells
const getSelectedCells = () => {
return gridRef.current.getSelectedRows();
};Cell Templates
Custom Cell Template
const cellTemplate = (props) => {
return (
<div>
<strong>{props.CustomerID}</strong>
</div>
);
};
<ColumnDirective
field='CustomerID'
headerText='Customer'
template={cellTemplate}
width='120'
/>Conditional Cell Styling
const cellTemplate = (props) => {
const color = props.Freight > 50 ? 'red' : 'green';
return (
<span style={{ color, fontWeight: 'bold' }}>
${props.Freight.toFixed(2)}
</span>
);
};
<ColumnDirective field='Freight' template={cellTemplate} />Cell with Action Button
const actionTemplate = (props) => {
return (
<div>
<button onClick={() => viewDetails(props.OrderID)}>View</button>
<button onClick={() => deleteRecord(props.OrderID)}>Delete</button>
</div>
);
};
<ColumnDirective
headerText='Actions'
template={actionTemplate}
width='150'
/>Cell Formatting
Number Formatting
<ColumnDirective
field='Freight'
headerText='Freight'
format='C2' // Currency with 2 decimals
textAlign='Right'
/>Date Formatting
<ColumnDirective
field='OrderDate'
headerText='Order Date'
type='date'
format='yMd' // Year-Month-Day
/>Percent Formatting
<ColumnDirective
field='Discount'
headerText='Discount'
format='P2' // Percent with 2 decimals
type='number'
/>Cell Events
Cell Selected Event
const cellSelected = (args) => {
console.log('Cell index:', args.cellIndex);
console.log('Row index:', args.rowIndex);
console.log('Cell value:', args.data[args.column.field]);
};
<GridComponent
dataSource={data}
cellSelected={cellSelected}
selectionSettings={{ mode: 'Cell' }}
>
{/* columns */}
</GridComponent>Query Cell Info Event
const queryCellInfo = (args) => {
if (args.data.Freight > 100) {
args.cell.style.backgroundColor = '#ffcccc';
args.cell.style.color = '#cc0000';
}
};
<GridComponent queryCellInfo={queryCellInfo}>
{/* columns */}
</GridComponent>Cell Edit Event
const onActionBegin = (args) => {
if (args.requestType === 'save') {
console.log('Cell edited:', args.data);
}
};
<GridComponent
dataSource={data}
actionBegin={onActionBegin}
editSettings={{ mode: 'Dialog' }}
>
{/* columns */}
</GridComponent>Programmatic Cell Edit
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const editCell = (rowIndex, columnName) => {
gridRef.current.startEdit({ rowIndex, columnName });
};
const saveCell = () => {
gridRef.current.endEdit();
};
return (
<div>
<button onClick={() => editCell(0, 'CustomerID')}>Edit First Cell</button>
<button onClick={saveCell}>Save</button>
<GridComponent ref={gridRef} dataSource={data}>
{/* columns */}
</GridComponent>
</div>
);
}
export default App;Cell Value Change
<ColumnDirective
field='Freight'
headerText='Freight'
editTemplate={
<input
type='number'
onChange={(e) => {
console.log('Value changed:', e.target.value);
}}
/>
}
/>Advanced Cell Operations
Cell-Level Validation
function GridWithCellValidation() {
const gridRef = useRef(null);
const onActionBegin = (args) => {
if (args.requestType === 'save') {
const newData = args.data;
// Validate Freight (must be > 0)
if (newData.Freight && newData.Freight <= 0) {
args.cancel = true;
alert('Freight must be greater than 0');
return;
}
// Validate CustomerID (must not be empty)
if (!newData.CustomerID || newData.CustomerID.trim() === '') {
args.cancel = true;
alert('Customer ID is required');
return;
}
}
};
return (
<GridComponent
ref={gridRef}
dataSource={data}
editSettings={{ mode: 'Dialog', allowEditing: true }}
actionBegin={onActionBegin}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
</ColumnsDirective>
<Inject services={[Edit]} />
</GridComponent>
);
}
export default GridWithCellValidation;Cell Styling Based on Values
function GridWithConditionalStyling() {
const queryCellInfo = (args) => {
if (args.column.field === 'Freight') {
const freight = args.data.Freight;
if (freight > 100) {
// High value - red
args.cell.style.backgroundColor = '#ffcccc';
args.cell.style.color = '#cc0000';
args.cell.style.fontWeight = 'bold';
} else if (freight > 50) {
// Medium value - yellow
args.cell.style.backgroundColor = '#fff9c4';
args.cell.style.color = '#f57f17';
} else {
// Low value - green
args.cell.style.backgroundColor = '#c8e6c9';
args.cell.style.color = '#2e7d32';
}
}
if (args.column.field === 'OrderDate') {
const orderDate = new Date(args.data.OrderDate);
const today = new Date();
const daysDiff = Math.floor((today - orderDate) / (1000 * 60 * 60 * 24));
if (daysDiff > 365) {
args.cell.style.opacity = '0.6'; // Old order
}
}
};
return (
<GridComponent
dataSource={data}
queryCellInfo={queryCellInfo}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
<ColumnDirective field='OrderDate' headerText='Order Date' type='date' format='yMd' width='120' />
</ColumnsDirective>
</GridComponent>
);
}
export default GridWithConditionalStyling;Custom Cell Templates with Buttons
function GridWithActionCells() {
const gridRef = useRef(null);
const actionTemplate = (props) => {
return (
<div style={{ display: 'flex', gap: '5px' }}>
<button
onClick={() => {
console.log('View:', props.OrderID);
// Show details modal
}}
style={{
padding: '4px 8px',
background: '#1976d2',
color: 'white',
border: 'none',
borderRadius: '3px',
cursor: 'pointer',
fontSize: '12px'
}}
>
View
</button>
<button
onClick={() => {
gridRef.current.deleteRecord(props.OrderID);
}}
style={{
padding: '4px 8px',
background: '#d32f2f',
color: 'white',
border: 'none',
borderRadius: '3px',
cursor: 'pointer',
fontSize: '12px'
}}
>
Delete
</button>
</div>
);
};
return (
<GridComponent
ref={gridRef}
dataSource={data}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective headerText='Actions' template={actionTemplate} width='120' />
</ColumnsDirective>
</GridComponent>
);
}
export default GridWithActionCells;Cell Range Selection and Operations
function GridWithCellRangeOps() {
const gridRef = useRef(null);
const [selectedRange, setSelectedRange] = useState(null);
const cellSelectionChange = (args) => {
const selectedCells = gridRef.current.getSelectedRowCellIndexes();
if (selectedCells.length > 0) {
setSelectedRange(selectedCells);
}
};
const sumSelectedCells = () => {
if (!selectedRange) return;
let sum = 0;
selectedRange.forEach(index => {
const record = gridRef.current.getCurrentViewRecords()[index.rowIndex];
if (record && record.Freight) {
sum += record.Freight;
}
});
alert(`Sum of selected freight: $${sum.toFixed(2)}`);
};
return (
<div>
<button onClick={sumSelectedCells} style={{ marginBottom: '10px' }}>
Sum Selected Cells
</button>
<GridComponent
ref={gridRef}
dataSource={data}
selectionSettings={{ type: 'Multiple', mode: 'Cell' }}
cellSelected={cellSelectionChange}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
</ColumnsDirective>
</GridComponent>
</div>
);
}
export default GridWithCellRangeOps;Cell Editing with Custom Dropdown
function GridWithDropdownCell() {
const statuses = ['Pending', 'Confirmed', 'Shipped', 'Delivered'];
const statusTemplate = (props) => {
return <span className={`status-${props.Status?.toLowerCase()}`}>{props.Status}</span>;
};
const statusEditTemplate = (props) => {
return (
<select value={props.Status} style={{ width: '100%', padding: '8px' }}>
{statuses.map(status => (
<option key={status} value={status}>{status}</option>
))}
</select>
);
};
return (
<GridComponent
dataSource={data}
editSettings={{ mode: 'Dialog', allowEditing: true }}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' isPrimaryKey={true} />
<ColumnDirective
field='Status'
headerText='Status'
width='120'
template={statusTemplate}
editTemplate={statusEditTemplate}
/>
</ColumnsDirective>
<Inject services={[Edit]} />
</GridComponent>
);
}
export default GridWithDropdownCell;Clipboard Operations in React Grid
Table of Contents
Overview
Clipboard operations enable copying and pasting data between the grid and external applications.
Copy and Paste
Copy Selected Cell
import { GridComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-grids';
// Select cell and press Ctrl+C to copy
<GridComponent
dataSource={data}
selectionSettings={{ mode: 'Cell', type: 'Multiple' }}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
</ColumnsDirective>
</GridComponent>Copy with Headers
const contextMenuItems = [
'Copy', // Copy selected cells
'CopyWithHeader' // Copy with column headers
];
<GridComponent
dataSource={data}
contextMenuItems={contextMenuItems}
>
{/* columns */}
<Inject services={[ContextMenu]} />
</GridComponent>Programmatic Copy
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const copySelectedCells = () => {
const selectedCells = gridRef.current.getSelectedRows();
const text = selectedCells.map(cell => cell.toString()).join('\n');
navigator.clipboard.writeText(text);
};
return (
<div>
<button onClick={copySelectedCells}>Copy</button>
<GridComponent ref={gridRef} dataSource={data}>
{/* columns */}
</GridComponent>
</div>
);
}
export default App;Paste Data
const handlePaste = (e) => {
e.preventDefault();
const clipboardData = e.clipboardData.getData('text');
const rows = clipboardData.split('\n');
rows.forEach(row => {
const values = row.split('\t');
// Process pasted data
gridRef.current.addRecord({
OrderID: values[0],
CustomerID: values[1],
Freight: parseFloat(values[2])
});
});
};
<GridComponent
dataSource={data}
beforePaste ={handlePaste}
>
{/* columns */}
</GridComponent>Clipboard Events
Before Copy Event
const beforeCopy = (args) => {
console.log('Copying:', args.data);
// Modify clipboard data if needed
};
<GridComponent
dataSource={data}
beforeCopy={beforeCopy}
>
{/* columns */}
</GridComponent>Advanced Operations
Copy Full Row
const copyFullRow = () => {
const selectedRecord = gridRef.current.getSelectedRecords()[0];
const rowText = Object.values(selectedRecord).join('\t');
navigator.clipboard.writeText(rowText);
};Copy Filtered Data
const copyFilteredData = () => {
const filteredData = gridRef.current.getCurrentViewRecords();
const csvData = filteredData.map(row =>
Object.values(row).join(',')
).join('\n');
navigator.clipboard.writeText(csvData);
};Copy with Formatting
const copyFormattedData = () => {
const selectedCells = gridRef.current.getSelectedRows();
const formattedData = selectedCells.map(row => ({
...row,
Freight: `$${row.Freight.toFixed(2)}`,
OrderDate: row.OrderDate?.toLocaleDateString()
}));
const csv = [
Object.keys(formattedData[0]).join(','),
...formattedData.map(row => Object.values(row).join(','))
].join('\n');
navigator.clipboard.writeText(csv);
};Custom Clipboard Behavior
Custom Copy Formatter
function GridWithCustomCopy() {
const gridRef = useRef(null);
const beforeCopy = (args) => {
// Format numbers with currency symbol
const formattedData = args.data.map(row => ({
...row,
Freight: new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(row.Freight)
}));
// Update args to use formatted data
args.data = formattedData;
};
return (
<GridComponent
ref={gridRef}
dataSource={data}
selectionSettings={{ mode: 'Cell', type: 'Multiple' }}
beforeCopy={beforeCopy}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
</ColumnsDirective>
</GridComponent>
);
}
export default GridWithCustomCopy;Copy with Headers
function GridWithCopyHeaders() {
const gridRef = useRef(null);
const copyWithHeaders = () => {
const columns = gridRef.current.getColumns();
const columnNames = columns.map(col => col.field).join('\t');
const selectedData = gridRef.current.getSelectedRecords();
const dataRows = selectedData.map(row =>
columns.map(col => row[col.field]).join('\t')
).join('\n');
const fullText = `${columnNames}\n${dataRows}`;
navigator.clipboard.writeText(fullText);
alert('Data copied with headers!');
};
return (
<div>
<button onClick={copyWithHeaders} style={{ marginBottom: '10px' }}>
Copy With Headers
</button>
<GridComponent
ref={gridRef}
dataSource={data}
selectionSettings={{ mode: 'Row', type: 'Multiple' }}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' />
</ColumnsDirective>
</GridComponent>
</div>
);
}
export default GridWithCopyHeaders;Smart Paste with Validation
function GridWithSmartPaste() {
const gridRef = useRef(null);
const [pasteError, setPasteError] = useState('');
const handlePaste = (e) => {
e.preventDefault();
setPasteError('');
const clipboardData = e.clipboardData.getData('text');
const rows = clipboardData.split('\n').filter(row => row.trim());
try {
const newRecords = [];
for (const row of rows) {
const [orderId, customerId, freight] = row.split('\t');
// Validate data
if (!orderId || !customerId || !freight) {
throw new Error('Invalid data format. Expected: OrderID, CustomerID, Freight');
}
if (isNaN(freight)) {
throw new Error(`Invalid freight value: ${freight}`);
}
newRecords.push({
OrderID: parseInt(orderId),
CustomerID: customerId.trim(),
Freight: parseFloat(freight)
});
}
// Add all valid records
newRecords.forEach(record => {
gridRef.current.addRecord(record);
});
alert(`Successfully pasted ${newRecords.length} records`);
} catch (error) {
setPasteError(`Paste error: ${error.message}`);
}
};
return (
<div>
{pasteError && (
<div style={{
background: '#ffebee',
color: '#c62828',
padding: '10px',
marginBottom: '10px',
borderRadius: '4px'
}}>
{pasteError}
</div>
)}
<div
onPaste={handlePaste}
style={{
padding: '20px',
background: '#f5f5f5',
marginBottom: '10px',
borderRadius: '4px',
textAlign: 'center',
color: '#666'
}}
>
Paste data here (Tab-delimited format)
</div>
<GridComponent
ref={gridRef}
dataSource={data}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
</ColumnsDirective>
</GridComponent>
</div>
);
}
export default GridWithSmartPaste;Copy to CSV Format
function GridWithCSVCopy() {
const gridRef = useRef(null);
const copyAsCSV = () => {
const columns = gridRef.current.getColumns();
const selectedRecords = gridRef.current.getSelectedRecords();
// Create CSV-safe values
const csvEscape = (value) => {
if (value === null || value === undefined) return '';
const stringValue = String(value);
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
return `"${stringValue.replace(/"/g, '""')}"`;
}
return stringValue;
};
// Build CSV
const headerRow = columns.map(col => csvEscape(col.headerText)).join(',');
const dataRows = selectedRecords.map(record =>
columns.map(col => csvEscape(record[col.field])).join(',')
).join('\n');
const csv = `${headerRow}\n${dataRows}`;
navigator.clipboard.writeText(csv);
alert('Data copied as CSV!');
};
return (
<div>
<button onClick={copyAsCSV} style={{ marginBottom: '10px' }}>
Copy as CSV
</button>
<GridComponent
ref={gridRef}
dataSource={data}
selectionSettings={{ mode: 'Row', type: 'Multiple' }}
allowSelection={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' format='C2' width='100' />
</ColumnsDirective>
</GridComponent>
</div>
);
}
export default GridWithCSVCopy;Copy with Custom Formatting
const beforeCopy = (args) => {
// Add timestamp and metadata
const timestamp = new Date().toLocaleString();
const metadata = `\n\n--- Copied on ${timestamp} ---`;
// Format each row
args.data = args.data.map(row => ({
OrderID: row.OrderID,
Customer: row.CustomerID,
Amount: `$${row.Freight.toFixed(2)}`,
Date: new Date(row.OrderDate).toLocaleDateString()
}));
args.copiedData = JSON.stringify(args.data, null, 2) + metadata;
};Columns in React Grid
Table of Contents
- Column Definition
- Column Types
- Column Width
- Column Templates
- Column Customization
- Advanced Column Features
Column Definition
Columns are defined using ColumnDirective components within ColumnsDirective. Each column maps to a field in your data source.
Basic Column Setup
import { ColumnDirective, ColumnsDirective, GridComponent } from '@syncfusion/ej2-react-grids';
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='OrderDate' headerText='Order Date' width='130' type='date' />
<ColumnDirective field='Freight' headerText='Freight' width='120' format='C2' />
</ColumnsDirective>
</GridComponent>Key Properties
| Property | Type | Purpose | Example |
|---|---|---|---|
field | string | Maps to data source field (required) | field='OrderID' |
headerText | string | Column header label | headerText='Order ID' |
width | string \ | number | Column width |
type | string | Data type for formatting | type='date' |
format | string | Number/date format | format='C2' |
textAlign | string | Text alignment | textAlign='Right' |
allowSorting | boolean | Enable sorting | allowSorting={true} |
allowFiltering | boolean | Enable filtering | allowFiltering={true} |
isPrimaryKey | boolean | Mark as primary key | isPrimaryKey={true} |
visible | boolean | Show/hide column | visible={true} |
allowGrouping | boolean | Enable grouping | allowGrouping={true} |
Column Types
String Type
Text values (default):
<ColumnDirective field='CustomerID' headerText='Customer' type='string' />Number Type
Numeric values:
<ColumnDirective field='Freight' headerText='Freight' type='number' format='C2' />Date Type
Date values:
<ColumnDirective field='OrderDate' headerText='Order Date' type='date' format='yMd' />DateTime Type
Date and time values:
<ColumnDirective field='OrderDate' headerText='Order Date' type='datetime' format='MM/dd/yyyy hh:mm a' />Boolean Type
True/false values:
<ColumnDirective field='Verified' headerText='Verified' type='boolean' displayAsCheckBox={true} />Checkbox Type
Selection checkboxes:
<ColumnDirective type='checkbox' width='50' />Column Width
Pixel Width
Fixed width in pixels:
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />Percentage Width
Responsive width as percentage:
<ColumnDirective field='OrderID' headerText='Order ID' width='20%' />
<ColumnDirective field='CustomerID' headerText='Customer' width='30%' />
<ColumnDirective field='Freight' headerText='Freight' width='25%' />
<ColumnDirective field='ShipCity' headerText='Ship City' width='25%' />Auto Width
Automatically sized based on content:
<ColumnDirective field='CustomerName' headerText='Customer' width='auto' />Column Resize
Allow users to resize columns:
<GridComponent allowResizing={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' allowResizing={true} />
</ColumnsDirective>
</GridComponent>Auto-Fit Columns
Automatically fit columns to content:
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Resize } from '@syncfusion/ej2-react-grids';
<GridComponent allowResizing={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' autoFit={true} />
</ColumnsDirective>
<Inject services={[Resize]} />
</GridComponent>Column Templates
Cell Template
Custom cell rendering:
const cellTemplate = (props) => {
return (
<div>
<strong>${props.Freight}</strong>
</div>
);
};
<ColumnDirective field='Freight' headerText='Freight' template={cellTemplate} width='150' />Header Template
Custom header rendering:
const headerTemplate = () => {
return <div><strong>Freight Amount</strong></div>;
};
<ColumnDirective field='Freight' headerText='Freight' headerTemplate={headerTemplate} width='150' />Edit Template
Custom editor for inline editing:
const editTemplate = (props) => {
return (
<input
type='number'
value={props.Freight}
onChange={(e) => props.Freight = parseFloat(e.target.value)}
/>
);
};
<ColumnDirective
field='Freight'
headerText='Freight'
template={cellTemplate}
editTemplate={editTemplate}
width='150'
/>Column Customization
Column Reordering
Allow users to reorder columns:
<GridComponent allowReordering={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' allowReordering={true} />
</ColumnsDirective>
</GridComponent>Column Spanning
Merge cells across columns:
const gridRef = useRef(null);
const queryCellInfo = (args) => {
if (args.data.OrderID === 10248) {
args.colSpan = 2; // Span 2 columns
}
};
<GridComponent ref={gridRef} queryCellInfo={queryCellInfo}>
{/* columns */}
</GridComponent>Frozen Columns
Keep columns visible while scrolling:
<ColumnDirective field='OrderID' headerText='Order ID' width='100' isFrozen={true} />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />Hidden Columns
Hide columns from display:
<ColumnDirective field='InternalNotes' headerText='Notes' visible={false} width='200' />Advanced Column Features
Column Menu
Enable column operations menu with filtering, sorting, grouping:
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Filter, Sort, Group, ColumnMenu } from '@syncfusion/ej2-react-grids';
<GridComponent showColumnMenu={true} allowFiltering={true} allowSorting={true} allowGrouping={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
</ColumnsDirective>
<Inject services={[Filter, Sort, Group, ColumnMenu]} />
</GridComponent>Column Menu Options:
- Filter
- Sort Ascending/Descending
- Group by column
- AutoFit
- Column Chooser
- Custom menu items
Foreign Key Column
Display related data from another source:
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, ForeignKey } from '@syncfusion/ej2-react-grids';
const employeeData = [
{ EmployeeID: 1, EmployeeName: 'Nancy Davolio' },
{ EmployeeID: 2, EmployeeName: 'Andrew Fuller' },
{ EmployeeID: 3, EmployeeName: 'Janet Leverling' }
];
function App() {
return (
<GridComponent dataSource={orders}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective
field='EmployeeID'
headerText='Employee'
foreignKeyField='EmployeeID'
foreignKeyValue='EmployeeName'
dataSource={employeeData}
width='150'
/>
<ColumnDirective field='Freight' headerText='Freight' format='C2' />
</ColumnsDirective>
<Inject services={[ForeignKey]} />
</GridComponent>
);
}
export default App;Foreign Key Features:
- Edit with dropdown selection
- Display lookup value instead of ID
- Works with filtering and sorting
- Supports hierarchical data
Column Chooser
Allow users to show/hide columns dynamically:
import { GridComponent, Inject, ColumnChooser, ColumnMenu } from '@syncfusion/ej2-react-grids';
<GridComponent toolbar={['ColumnChooser']} showColumnMenu={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' showColumnChooser={true} />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' showColumnChooser={true} />
<ColumnDirective field='OrderDate' headerText='Order Date' width='130' showColumnChooser={true} />
</ColumnsDirective>
<Inject services={[ColumnChooser, ColumnMenu]} />
</GridComponent>Multi-Column Header
Group columns under a single header:
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective headerText='Order Details' columns={[
{ field: 'OrderID', headerText: 'Order ID', width: '100' },
{ field: 'OrderDate', headerText: 'Order Date', width: '130' }
]} />
<ColumnDirective headerText='Shipping' columns={[
{ field: 'ShipCity', headerText: 'City', width: '120' },
{ field: 'ShipCountry', headerText: 'Country', width: '150' }
]} />
</ColumnsDirective>
</GridComponent>Column Styling
Apply custom CSS classes and styling:
<ColumnDirective
field='Freight'
headerText='Freight'
customClass='high-value-cell'
cssClass='freight-column'
width='120'
/>
<style>{`
.freight-column {
background-color: #f0f0f0;
}
.high-value-cell {
color: green;
font-weight: bold;
}
`}</style>Format Columns
Apply number and date formatting:
// Currency format
<ColumnDirective field='Freight' format='C2' />
// Date format
<ColumnDirective field='OrderDate' format='yMd' />
// Percent format
<ColumnDirective field='Percentage' format='P2' />
// Custom number format
<ColumnDirective field='Quantity' format='N3' /> {/* 1,234.567 */}Column Validation
Validate column data during editing:
<GridComponent editSettings={{ mode: 'Dialog', allowEditing: true }}>
<ColumnsDirective>
<ColumnDirective
field='OrderID'
headerText='Order ID'
isprimarykey={true}
validationRules={{ required: true }}
/>
<ColumnDirective
field='Freight'
headerText='Freight'
validationRules={{ min: 0, max: 10000, required: true }}
/>
<ColumnDirective
field='CustomerID'
headerText='Customer'
validationRules={{ required: true, minLength: 3, maxLength: 5 }}
/>
</ColumnsDirective>
</GridComponent>Row Spanning
Span a cell across multiple rows:
const gridRef = useRef(null);
const rowDataBound = (args) => {
if (args.data.OrderID === 10248) {
args.rowSpan = { OrderID: 2 }; // Span 2 rows
}
};
<GridComponent ref={gridRef} rowDataBound={rowDataBound}>
{/* columns */}
</GridComponent>Column Lock
Lock columns from interacting with other columns:
<ColumnDirective field='OrderID' headerText='Order ID' allowGrouping={false} allowSorting={false} />Stack Column
Stack columns for responsive views:
<GridComponent dataSource={stackedHeaderData}>
<ColumnsDirective>
<ColumnDirective
field="CustomerID"
headerText="Customer ID"
width="160"
textAlign="Right"
isPrimaryKey={true}
></ColumnDirective>
<ColumnDirective
field="CustomerName"
headerText="Name"
width="150"
minWidth="120"
></ColumnDirective>
<ColumnDirective
columns={[
{
field: 'OrderID',
headerText: 'ID',
textAlign: 'Right',
width: 110,
},
{
field: 'OrderDate',
headerText: 'Date',
textAlign: 'Right',
width: 110,
},
]}
headerText="Order Details"
textAlign="Center"
/>
</ColumnsDirective>
</GridComponent>Command Column in React Grid
Table of Contents
- Overview
- Enable Command Column
- Built-in Commands
- Custom Commands
- Command Events
- Conditional Commands
- Styling Commands
- Complete Example
- Best Practives
Overview
Command column provides buttons for common grid actions like Edit, Delete, and custom operations. It simplifies row-level interactions without needing custom templates.
---
Enable Command Column
Basic Setup
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Edit, Toolbar } from '@syncfusion/ej2-react-grids';
<GridComponent
dataSource={data}
editSettings={{ mode: 'Dialog', allowEditing: true, allowDeleting: true }}
>
<ColumnsDirective>
<ColumnDirective type='checkbox' width='50' />
<ColumnDirective field='OrderID' headerText='Order ID' width='100' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective
headerText='Actions'
width='150'
commands={[
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Save', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Cancel', buttonOption: { cssClass: 'e-flat' } }
]}
/>
</ColumnsDirective>
<Inject services={[Edit, Toolbar]} />
</GridComponent>---
Built-in Commands
Available Built-in Commands
| Command | Available In | Action |
|---|---|---|
Edit | Normal mode | Start editing row |
Delete | Normal mode | Delete row |
Save | Edit mode | Save changes |
Cancel | Edit mode | Cancel editing |
Edit/Delete Commands
<ColumnDirective
headerText='Edit/Delete'
width='120'
commands={[
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } }
]}
/>Save/Cancel Commands
<ColumnDirective
headerText='Save/Cancel'
width='120'
commands={[
{ type: 'Save', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Cancel', buttonOption: { cssClass: 'e-flat' } }
]}
/>All Commands
<ColumnDirective
headerText='Actions'
width='200'
commands={[
{ type: 'Edit', buttonOption: { cssClass: 'e-flat', iconCss: 'e-icons e-edit' } },
{ type: 'Save', buttonOption: { cssClass: 'e-flat', iconCss: 'e-icons e-save' } },
{ type: 'Cancel', buttonOption: { cssClass: 'e-flat', iconCss: 'e-icons e-cancel' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat e-flat-danger', iconCss: 'e-icons e-delete' } }
]}
/>---
Custom Commands
Duplicate Row Command
<ColumnDirective
headerText='Actions'
width='150'
commands={[
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{
buttonOption: {
content: 'Copy',
cssClass: 'e-flat',
click: onDuplicate
}
},
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } }
]}
/>
const onDuplicate = (args) => {
const gridInstance = gridRef.current;
const rowData = args.rowData;
const newData = { ...rowData };
delete newData[gridInstance.getPrimaryKeyFieldNames()[0]];
gridInstance.addRecord(newData);
};View Details Command
const onViewDetails = (args) => {
const rowData = args.rowData;
showDetailModal(rowData);
};
<ColumnDirective
headerText='Actions'
width='150'
commands={[
{
buttonOption: {
content: 'Details',
cssClass: 'e-flat e-info',
click: onViewDetails
}
},
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } }
]}
/>Download/Export Command
const onDownload = (args) => {
const rowData = args.rowData;
const csv = convertToCSV(rowData);
downloadCSV(csv, `order-${rowData.OrderID}.csv`);
};
<ColumnDirective
headerText='Actions'
width='150'
commands={[
{
buttonOption: {
content: 'Download',
cssClass: 'e-flat e-success',
iconCss: 'e-icons e-download',
click: onDownload
}
},
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } }
]}
/>Multi-Action Button
const commandOptions = [
{
buttonOption: {
content: 'More Actions',
cssClass: 'e-flat e-outline',
click: (args) => showActionMenu(args)
}
}
];
const showActionMenu = (args) => {
const menu = new ContextMenu({
target: args.target,
items: [
{ text: 'Print', id: 'print', click: () => printRow(args.rowData) },
{ text: 'Email', id: 'email', click: () => emailRow(args.rowData) },
{ text: 'Archive', id: 'archive', click: () => archiveRow(args.rowData) },
{ text: 'Share', id: 'share', click: () => shareRow(args.rowData) }
]
});
menu.open(args.target);
};
<ColumnDirective
headerText='Actions'
width='150'
commands={commandOptions}
/>---
Command Events
Click Event
const handleCommandClick = (args) => {
console.log('Command type:', args.commandType);
console.log('Row data:', args.rowData);
console.log('Button element:', args.target);
};
<GridComponent
dataSource={data}
commandClick={handleCommandClick}
>
{/* columns */}
</GridComponent>Command Type Checking
const handleCommand = (args) => {
if (args.commandType === 'Edit') {
console.log('Edit command:', args.rowData);
} else if (args.commandType === 'Delete') {
console.log('Delete command:', args.rowData);
} else if (args.commandType === 'Custom') {
console.log('Custom command:', args.commandType);
}
};
<GridComponent commandClick={handleCommand}>
{/* columns */}
</GridComponent>---
Conditional Commands
Show/Hide Based on Row Data
<ColumnDirective
headerText='Actions'
width='150'
template={(props) => (
<div>
<button
className='e-btn e-small'
onClick={() => editRow(props)}
style={{ marginRight: '5px' }}
>
Edit
</button>
{props.Status !== 'Completed' && (
<button
className='e-btn e-small e-danger'
onClick={() => deleteRow(props)}
>
Delete
</button>
)}
{props.Status === 'Pending' && (
<button
className='e-btn e-small e-success'
onClick={() => completeRow(props)}
>
Complete
</button>
)}
</div>
)}
/>Role-Based Commands
const userRole = getCurrentUserRole();
<ColumnDirective
headerText='Actions'
width='150'
commands={
userRole === 'admin'
? [
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ type: 'Delete', buttonOption: { cssClass: 'e-flat' } },
{ buttonOption: { content: 'Audit', cssClass: 'e-flat', click: viewAudit } }
]
: [
{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } },
{ buttonOption: { content: 'View', cssClass: 'e-flat', click: viewDetails } }
]
}
/>Status-Based Commands
function getCommandsForStatus(status) {
const baseCommands = [{ type: 'Edit', buttonOption: { cssClass: 'e-flat' } }];
if (status === 'Draft') {
baseCommands.push({ type: 'Delete', buttonOption: { cssClass: 'e-flat' } });
}
if (status === 'Pending') {
baseCommands.push({
buttonOption: { content: 'Approve', cssClass: 'e-flat e-success', click: approveRow }
});
}
if (status === 'Approved') {
baseCommands.push({
buttonOption: { content: 'Publish', cssClass: 'e-flat e-info', click: publishRow }
});
}
return baseCommands;
}
<ColumnDirective
headerText='Actions'
width='200'
template={(props) => {
const commands = getCommandsForStatus(props.Status);
return (
<div>
{commands.map((cmd, i) => (
<button key={i} className='e-btn e-small'>
{cmd.type || cmd.buttonOption.content}
</button>
))}
</div>
);
}}
/>---
Styling Commands
Button Styling
<ColumnDirective
headerText='Actions'
width='200'
commands={[
{
type: 'Edit',
buttonOption: {
cssClass: 'e-flat e-outline',
iconCss: 'e-icons e-edit'
}
},
{
type: 'Delete',
buttonOption: {
cssClass: 'e-flat e-danger',
iconCss: 'e-icons e-delete'
}
},
{
buttonOption: {
content: 'Archive',
cssClass: 'e-flat e-warning',
iconCss: 'e-icons e-archive'
}
}
]}
/>CSS Classes
/* Button sizes */
.e-btn.e-small { padding: 4px 8px; font-size: 12px; }
.e-btn.e-large { padding: 12px 16px; font-size: 16px; }
/* Button styles */
.e-btn.e-flat { background-color: transparent; border: 1px solid #ddd; }
.e-btn.e-outline { border: 2px solid currentColor; }
.e-btn.e-primary { background-color: #007bff; color: white; }
.e-btn.e-success { background-color: #28a745; color: white; }
.e-btn.e-danger { background-color: #dc3545; color: white; }
.e-btn.e-warning { background-color: #ffc107; color: black; }
.e-btn.e-info { background-color: #17a2b8; color: white; }
/* Hover effects */
.e-btn:hover { opacity: 0.8; cursor: pointer; }
.e-btn:disabled { opacity: 0.5; cursor: not-allowed; }Inline Styling
const commandButtonStyle = {
padding: '6px 10px',
fontSize: '12px',
marginRight: '4px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
};
const editButtonStyle = { ...commandButtonStyle, backgroundColor: '#007bff', color: 'white' };
const deleteButtonStyle = { ...commandButtonStyle, backgroundColor: '#dc3545', color: 'white' };
<ColumnDirective
headerText='Actions'
width='150'
template={(props) => (
<div>
<button style={editButtonStyle} onClick={() => editRow(props)}>Edit</button>
<button style={deleteButtonStyle} onClick={() => deleteRow(props)}>Delete</button>
</div>
)}
/>---
Complete Example
import React, { useRef } from 'react';
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Edit, Toolbar } from '@syncfusion/ej2-react-grids';
function GridWithCommands() {
const gridRef = useRef(null);
const handleCommandClick = (args) => {
switch (args.commandType) {
case 'Edit':
console.log('Editing:', args.rowData);
break;
case 'Delete':
if (window.confirm('Are you sure you want to delete this record?')) {
gridRef.current.deleteRecord();
}
break;
default:
console.log('Custom command:', args.commandType);
}
};
const onDuplicate = (args) => {
const newData = { ...args.rowData };
delete newData.OrderID;
gridRef.current.addRecord(newData);
};
const onViewDetails = (args) => {
alert(`Order ID: ${args.rowData.OrderID}\nCustomer: ${args.rowData.CustomerID}`);
};
return (
<GridComponent
ref={gridRef}
dataSource={data}
editSettings={{ mode: 'Dialog', allowEditing: true, allowDeleting: true }}
commandClick={handleCommandClick}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' isPrimaryKey={true} />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' />
<ColumnDirective
headerText='Actions'
width='250'
commands={[
{
type: 'Edit',
buttonOption: { cssClass: 'e-flat', iconCss: 'e-icons e-edit' }
},
{
buttonOption: {
content: 'Duplicate',
cssClass: 'e-flat e-info',
click: onDuplicate
}
},
{
buttonOption: {
content: 'Details',
cssClass: 'e-flat e-primary',
click: onViewDetails
}
},
{
type: 'Delete',
buttonOption: { cssClass: 'e-flat e-danger', iconCss: 'e-icons e-delete' }
}
]}
/>
</ColumnsDirective>
<Inject services={[Edit, Toolbar]} />
</GridComponent>
);
}
export default GridWithCommands;---
Best Practices
1. Keep command column width fixed (~150-250px) 2. Order commands logically (Edit → Save/Cancel, Delete at end) 3. Use icons with text for clarity 4. Confirm destructive actions (delete) 5. Disable commands when not applicable 6. Show loading state for async operations 7. Provide keyboard shortcuts (accessibility) 8. Test with touch devices (small button sizing) 9. Group related commands visually 10. Provide tooltips for clarity
Context Menu in React Grid
Table of Contents
Overview
Context menu provides quick access to common operations when right-clicking on grid cells or rows.
Built-in Context Menu
Enable Context Menu
import { GridComponent, Inject, ContextMenu } from '@syncfusion/ej2-react-grids';
<GridComponent dataSource={data} contextMenuItems={['Copy', 'ExcelExport', 'PdfExport']}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
</ColumnsDirective>
<Inject services={[ContextMenu]} />
</GridComponent>Built-in Menu Items
const contextMenuItems = [
'Copy', // Copy selected cells
'CopyWithHeader', // Copy with column headers
'ExcelExport', // Export selection to Excel
'PdfExport', // Export selection to PDF
'CsvExport', // Export selection to CSV
'Edit', // Edit selected row
'Delete', // Delete selected row
'Save', // Save edited row
'Cancel', // Cancel editing
'PdfExport',
'ExcelExport'
];
<GridComponent contextMenuItems={contextMenuItems}>
{/* columns */}
<Inject services={[ContextMenu, Edit, ExcelExport, PdfExport]} />
</GridComponent>Custom Context Menu
Add Custom Menu Items
const customContextMenu = [
{
text: 'Copy',
target: '.e-gridcontent',
id: 'grid-copy'
},
{
text: 'View Details',
target: '.e-gridcontent',
id: 'grid-details'
},
{
text: 'Send Email',
target: '.e-gridcontent',
id: 'grid-email'
},
{
text: 'Generate Report',
target: '.e-gridcontent',
id: 'grid-report'
}
];
<GridComponent
dataSource={data}
contextMenuItems={customContextMenu}
contextMenuClick={handleContextMenuClick}
>
{/* columns */}
<Inject services={[ContextMenu]} />
</GridComponent>Context Menu with Icons
const contextMenuItems = [
{
text: 'Copy',
iconCss: 'e-icons e-copy',
target: '.e-gridcontent',
id: 'grid-copy'
},
{
text: 'Edit',
iconCss: 'e-icons e-edit',
target: '.e-gridcontent',
id: 'grid-edit'
},
{
text: 'Delete',
iconCss: 'e-icons e-delete',
target: '.e-gridcontent',
id: 'grid-delete'
}
];
<GridComponent contextMenuItems={contextMenuItems}>
{/* columns */}
</GridComponent>Submenu Items
const contextMenuItems = [
{
text: 'Export',
id: 'grid-export',
items: [
{ text: 'Excel', id: 'grid-excel' },
{ text: 'PDF', id: 'grid-pdf' },
{ text: 'CSV', id: 'grid-csv' }
]
},
{
text: 'Tools',
id: 'grid-tools',
items: [
{ text: 'Sort', id: 'grid-sort' },
{ text: 'Filter', id: 'grid-filter' }
]
}
];
<GridComponent contextMenuItems={contextMenuItems}>
{/* columns */}
</GridComponent>Event Handling
Handle Context Menu Click
const contextMenuClick = (args) => {
if (args.item.id === 'grid-copy') {
// Custom copy logic
console.log('Copying selected data...');
}
if (args.item.id === 'grid-details') {
// Show details dialog
const selectedRecord = gridRef.current.getSelectedRecords()[0];
showDetailsDialog(selectedRecord);
}
if (args.item.id === 'grid-email') {
// Send email logic
const selectedRecord = gridRef.current.getSelectedRecords()[0];
sendEmailTo(selectedRecord.CustomerID);
}
};
<GridComponent
dataSource={data}
contextMenuItems={customMenu}
contextMenuClick={contextMenuClick}
>
{/* columns */}
<Inject services={[ContextMenu]} />
</GridComponent>Context Menu Before Open
const beforeOpen = (args) => {
console.log('Context menu opening at:', args.top, args.left);
// Conditionally show/hide items
const hasSelection = gridRef.current.getSelectedRows().length > 0;
// args.items can be modified here
};
<GridComponent
dataSource={data}
beforeOpen={beforeOpen}
contextMenuItems={contextMenuItems}
>
{/* columns */}
<Inject services={[ContextMenu]} />
</GridComponent>Programmatic Context Menu Trigger
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const triggerContextMenu = (e) => {
// Right-click triggers context menu automatically
// Or programmatically:
// gridRef.current.contextMenuModule.contextMenu.show(e);
};
return (
<GridComponent ref={gridRef} dataSource={data}>
{/* columns */}
<Inject services={[ContextMenu]} />
</GridComponent>
);
}
export default App;Data Binding in React Grid
Table of Contents
- Overview
- Local Data Binding
- Remote Data Binding
- DataManager Configuration
- Loading Indicators
- Handling Data Source Changes
Overview
Data binding is the process of connecting the Grid component to a data source. The React Grid supports both local data (JavaScript arrays) and remote data (REST API services) through the DataManager component.
⚠️ Data Source Selection Rule — Choose the Right Approach
Always default to local array data unless the prompt explicitly mentions a remote API, REST endpoint, or server-side data source.
| Scenario | Use | How |
|---|---|---|
| Prompt provides sample data, static list, or does NOT mention an API | Local array | dataSource={data} where data is a plain JS array |
| Prompt explicitly mentions REST API, remote endpoint, server-side, or URL | DataManager | dataManager = new DataManager({ url: '...', adaptor: new UrlAdaptor() }) |
- ✅ Local array: Used when data is hardcoded, provided inline, or when no remote source is mentioned. No imports from
@syncfusion/ej2-dataneeded. - ✅ DataManager: Used only when the prompt explicitly mentions connecting to a REST API, remote URL, or server-side data source.
- ❌ Never use
DataManagerwith a remote URL when the prompt asks for local/static data — it will cause data not to load because the URL does not exist.
Local Data Binding
Binding Array Data
The simplest way to bind data is to pass a JavaScript array to the dataSource property:
import { ColumnDirective, ColumnsDirective, GridComponent } from '@syncfusion/ej2-react-grids';
import React from 'react';
const data = [
{ OrderID: 10248, CustomerID: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerID: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerID: 'HANAR', Freight: 65.83 }
];
function App() {
return (
<GridComponent dataSource={data}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='120' format='C2' />
</ColumnsDirective>
</GridComponent>
);
}
export default App;Dynamic Data Updates
Update grid data dynamically using state management:
import { ColumnDirective, ColumnsDirective, GridComponent } from '@syncfusion/ej2-react-grids';
import React, { useState } from 'react';
function App() {
const [gridData, setGridData] = useState([
{ OrderID: 10248, CustomerID: 'VINET', Freight: 32.38 }
]);
const addRow = () => {
setGridData([...gridData, { OrderID: 10251, CustomerID: 'VICTE', Freight: 41.34 }]);
};
return (
<div>
<button onClick={addRow}>Add Row</button>
<GridComponent dataSource={gridData}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='120' format='C2' />
</ColumnsDirective>
</GridComponent>
</div>
);
}
export default App;Remote Data Binding
Remote DataManager Quick Checklist
Only use DataManager when the prompt explicitly mentions a REST API, remote endpoint, or server-side data. For local/static data use a plain array.
1. Import DataManager and the appropriate adaptor from @syncfusion/ej2-data 2. Always replace the `url` with the actual API endpoint — never leave a placeholder or demo URL 3. Match `field` names on `<ColumnDirective>` exactly to the JSON property names returned by the API — any mismatch causes empty columns 4. Choose the correct adaptor based on your API response shape:
UrlAdaptor→ API returns{ result: [...], count: N }WebApiAdaptor→ API returns a plain array or{ value: [...], Count: N }
5. For paging with UrlAdaptor, the API must return a count field with the total record count — without it the pager shows incorrect page numbers 6. Add loadingIndicator={{ indicatorType: 'Spinner' }} or 'Shimmer' only when the prompt asks for a loading indicator or the scenario explicitly involves async API fetching — never add it for local data 7. With UrlAdaptor, all operations (filter, search, sort, page) are automatically sent server-side — inject Filter, Search, Sort, Page modules as normal; no custom query logic needed 8. For server-side error handling use the actionFailure event: <GridComponent actionFailure={(args) => console.error(args.error)} />
Using DataManager
Connect to remote REST API endpoints using DataManager:
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { ColumnDirective, ColumnsDirective, GridComponent, Inject, Page } from '@syncfusion/ej2-react-grids';
import React from 'react';
function App() {
const data = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
return (
<GridComponent dataSource={data} allowPaging={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='120' format='C2' />
</ColumnsDirective>
<Inject services={[Page]} />
</GridComponent>
);
}
export default App;Adaptor Types
The Grid supports multiple adaptor types for different backend architectures:
1. UrlAdaptor - Standard REST API
const data = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});2. ODataV4Adaptor - OData v4 services with advanced filtering
import { ODataV4Adaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});3. WebApiAdaptor - ASP.NET Web API
import { WebApiAdaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
crossDomain: true
});4. GraphQLAdaptor - GraphQL endpoints
import { GraphQLAdaptor } from '@syncfusion/ej2-data';
const data = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor(),
query: `{
orders {
id
customer
amount
date
}
}`
});For detailed examples of all adaptor types, see references/adaptors.md
Error Handling
Handle data loading errors gracefully:
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { GridComponent, Inject, Page } from '@syncfusion/ej2-react-grids';
import React, { useState } from 'react';
function App() {
const [error, setError] = useState(null);
const data = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
const handleActionFailure = (args) => {
console.error('Data loading failed:', args.error);
setError('Failed to load data. Please try again.');
};
return (
<div>
{error && <div style={{ color: 'red' }}>{error}</div>}
<GridComponent
dataSource={data}
actionFailure={handleActionFailure}
allowPaging={true}
>
{/* columns */}
<Inject services={[Page]} />
</GridComponent>
</div>
);
}
export default App;Loading States
Display loading indicators while data is being fetched:
import { GridComponent } from '@syncfusion/ej2-react-grids';
import { Spinner } from '@syncfusion/ej2-react-popups';
import React, { useState } from 'react';
function App() {
const [isLoading, setIsLoading] = useState(false);
const handleDataBound = () => {
setIsLoading(false);
};
const handleActionBegin = (args) => {
if (args.requestType === 'beforesend') {
setIsLoading(true);
}
};
return (
<div>
{isLoading && <Spinner type='Bootstrap'></Spinner>}
<GridComponent
dataSource={data}
actionBegin={handleActionBegin}
dataBound={handleDataBound}
>
{/* columns */}
</GridComponent>
</div>
);
}
export default App;Shimmer Loading
Show skeleton loading during data fetch:
function ShimmerGrid() {
const [isLoading, setIsLoading] = useState(true);
setTimeout(() => setIsLoading(false), 2000);
if (isLoading) {
return (
<div style={{ padding: '20px' }}>
{[...Array(5)].map((_, i) => (
<div key={i} style={{
height: '40px',
marginBottom: '10px',
backgroundColor: '#e0e0e0',
borderRadius: '4px',
animation: 'pulse 1.5s infinite'
}} />
))}
<style>{`
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
`}</style>
</div>
);
}
return <GridComponent dataSource={data}>{/* columns */}</GridComponent>;
}Custom Adaptor
Create a custom adaptor for specialized data handling:
import { DataManager, Adaptor } from '@syncfusion/ej2-data';
class CustomAdaptorExample extends Adaptor {
constructor() {
super();
this.baseUrl = 'url';
}
// Override processResponse to transform data
processResponse(response: any, dm?: DataManager, query?: Query, xhr?: XMLHttpRequest, request?: Request, params?: any): any {
const result = super.processResponse(response, dm, query, xhr, request, params);
// Transform result
if (result && result.result) {
result.result = result.result.map((item: any) => ({
...item,
processed: true
}));
}
return result;
}
}
// Use in DataManager
const dataManager = new DataManager({
adaptor: new CustomAdaptorExample(),
crossDomain: true
});DataManager Configuration
Choosing the Right Adaptor
| Adaptor | Use When | Expected API Response |
|---|---|---|
UrlAdaptor | Your API is built to work with Syncfusion DataManager | { result: [...], count: N } |
WebApiAdaptor | Standard Web API returning plain arrays or OData-like responses | { value: [...], Count: N } or plain array |
ODataAdaptor | OData v3 service | OData XML/JSON format |
ODataV4Adaptor | OData v4 service | OData v4 JSON format |
JsonAdaptor | Local array already in memory | Plain JS array |
⚠️ If data is not loading, the most common causes are:
1. Wrong URL — the url is a placeholder or demo URL, not your real API2. Wrong adaptor — your API returns a plain array but you are usingUrlAdaptor(useWebApiAdaptorinstead)
3. Wrong field names —fieldvalues on<ColumnDirective>don't match JSON property names returned by the API
4. Missing `count` —UrlAdaptorrequires the response to include"count"for paging to work
Offline Mode
Load data once and work offline:
const dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
offline: true
});CRUD Operations
DataManager supports automatic CRUD operations:
const dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
insertUrl: 'url',
updateUrl: 'url',
removeUrl: 'url'
});Loading Indicators
Display loading state while fetching remote data using loadingIndicator:
Spinner Indicator (Default)
const loadingIndicator = { indicatorType: 'Spinner' };
<GridComponent
dataSource={dataManager}
loadingIndicator={loadingIndicator}
allowPaging={true}
>
{/* columns */}
</GridComponent>Shimmer Indicator
const loadingIndicator = { indicatorType: 'Shimmer' };
<GridComponent
dataSource={dataManager}
loadingIndicator={loadingIndicator}
allowPaging={true}
>
{/* columns */}
</GridComponent>Control Loading State
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const showLoading = () => {
// Grid automatically shows loading while fetching
};
return (
<GridComponent
ref={gridRef}
dataSource={dataManager}
loadingIndicator={{ indicatorType: 'Spinner' }}
>
{/* columns */}
</GridComponent>
);
}
export default App;Handling Data Source Changes
Update Data Source
Change data source dynamically:
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const changeData = () => {
const newData = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
gridRef.current.setProperties({ dataSource: newData });
};
return (
<div>
<button onClick={changeData}>Switch Data Source</button>
<GridComponent ref={gridRef} dataSource={initialData}>
{/* columns */}
</GridComponent>
</div>
);
}
export default App;Refresh Data
Reload data from the source:
const refreshData = () => {
gridRef.current.refresh();
};Query with Additional Parameters
const dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});
// Add query parameters
dataManager.executeQuery(
new Query().where('CustomerID', 'equal', 'VINET')
);Excel Export in React Grid
Table of Contents
Overview
Export grid data to Excel format with customization options for formatting, templates, and file naming.
Basic Export
Enable Excel Export
import { GridComponent, Inject, ExcelExport } from '@syncfusion/ej2-react-grids';
<GridComponent dataSource={data} allowExcelExport={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' />
</ColumnsDirective>
<Inject services={[ExcelExport]} />
</GridComponent>Add Export Button
import { Toolbar } from '@syncfusion/ej2-react-grids';
function App() {
const gridRef = useRef<GridComponent>(null);
const toolbarClick = (args: any) => {
if (args.item.id === 'Grid_excelexport') {
gridRef.current!.excelExport();
}
};
return (
<GridComponent
id='Grid'
dataSource={data}
toolbar={['ExcelExport']}
allowExcelExport={true}
toolbarClick={toolbarClick}
>
{/* columns */}
<Inject services={[Toolbar, ExcelExport]} />
</GridComponent>
);
}Programmatic Export
import React, { useRef } from 'react';
function App() {
const gridRef = useRef(null);
const exportToExcel = () => {
gridRef.current.excelExport();
};
return (
<div>
<button onClick={exportToExcel}>Export to Excel</button>
<GridComponent
ref={gridRef}
dataSource={data}
allowExcelExport={true}
>
{/* columns */}
<Inject services={[ExcelExport]} />
</GridComponent>
</div>
);
}
export default App;Export Configuration
Set Export File Name
const exportToExcel = () => {
const excelExportProperties = {
fileName: 'orders.xlsx'
};
gridRef.current.excelExport(excelExportProperties);
};Export Selected Records
const exportSelectedRows = () => {
const excelExportProperties = {
dataSource: gridRef.current.getSelectedRecords()
};
gridRef.current.excelExport(excelExportProperties);
};Export Filtered Data
const exportFilteredData = () => {
const excelExportProperties = {
dataSource: gridRef.current.getCurrentViewRecords()
};
gridRef.current.excelExport(excelExportProperties);
};Customize Export Columns
const exportToExcel = () => {
const excelExportProperties = {
columns: [
{ field: 'OrderID' },
{ field: 'CustomerID' },
{ field: 'Freight' }
]
};
gridRef.current.excelExport(excelExportProperties);
};Export with Templates
Include Column Templates
import { ColumnDirective, ColumnsDirective, GridComponent, Inject,
ExcelExport, Toolbar } from '@syncfusion/ej2-react-grids';
import * as React from 'react';
import { employeeData } from './datasource';
function App() {
let grid;
const toolbar = ['ExcelExport'];
const imageTemplate = (props) => {
return (
<div className="image">
<img
src={'data:image/jpeg;base64,' + props.EmployeeImage}
alt={props.EmployeeID}
/>
</div>
);
};
const mailTemplate = (props) => {
return (
<div className="link">
<a href={'mailto:' + props.EmailID}>{props.EmailID}</a>
</div>
);
};
const toolbarClick = (args) => {
if (grid && args['item'].id === 'ColumnTemplateGrid_excelexport') {
grid.excelExport();
}
};
const excelQueryCellInfo = (args) => {
if (args.column.headerText === 'Employee Image') {
args.image = {
base64: args.data.EmployeeImage,
height: 70,
width: 70,
};
}
if (args.column.headerText === 'Email ID') {
args.hyperLink = {
target: 'mailto:' + args.data.EmailID,
displayText: args.data.EmailID,
};
}
};
return (
<GridComponent id="ColumnTemplateGrid" ref={(g) => (grid = g)} dataSource={employeeData}
toolbar={toolbar} allowExcelExport={true} toolbarClick={toolbarClick} excelQueryCellInfo={excelQueryCellInfo}
height="260">
<ColumnsDirective>
<ColumnDirective headerText="Employee Image" width="180" template={imageTemplate} textAlign="Center" />
<ColumnDirective field="EmployeeID" headerText="Employee ID" width="125" />
<ColumnDirective field="FirstName" headerText="Name" width="120" />
<ColumnDirective field="EmailID" headerText="Email ID" template={mailTemplate} width="170" />
</ColumnsDirective>
<Inject services={[Toolbar, ExcelExport]} />
</GridComponent>
);
}
export default App;Custom Export Format
const onBeforeExcelExport = (args) => {
// Customize workbook before export
args.workbook.worksheets[0].name = 'Orders';
};
<GridComponent
dataSource={data}
beforeExcelExport={onBeforeExcelExport}
allowExcelExport={true}
>
{/* columns */}
<Inject services={[ExcelExport]} />
</GridComponent>Server-Side Export
Enable Server-Side Excel Export
const onActionBegin = (args) => {
if (args.requestType === 'excel') {
// Send export request to server
fetch('/api/grid/export-excel', {
method: 'POST',
body: JSON.stringify(args.gridObj.getCurrentViewRecords())
})
.then(response => response.blob())
.then(blob => {
// Trigger download
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'orders.xlsx';
a.click();
});
}
};
<GridComponent
dataSource={data}
actionBegin={onActionBegin}
>
{/* columns */}
</GridComponent>Filter Setup and Basic Configuration
Table of Contents
- Setup Filtering Module
- Enable Filtering
- Filter Types Overview
- Disable Filtering for Columns
- Quick Reference
---
Setup Filtering Module
Filtering requires injecting the Filter module into the Grid's services. Without this module, filtering features are unavailable.
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Filter } from '@syncfusion/ej2-react-grids';
<GridComponent dataSource={data} allowFiltering={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective field='CustomerID' headerText='Customer' width='120' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' />
</ColumnsDirective>
<Inject services={[Filter]} /> {/* REQUIRED for filtering */}
</GridComponent>---
Enable Filtering
Set allowFiltering={true} on the Grid component to activate filtering UI. Configure behavior via filterSettings property.
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Filter, Page } from '@syncfusion/ej2-react-grids';
<GridComponent
dataSource={data}
allowFiltering={true} // Enable filtering
allowPaging={true}
pageSettings={{ pageSize: 6 }}
allowSorting={true}
>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' textAlign='Right' />
<ColumnDirective field='CustomerID' headerText='Customer ID' width='100' />
<ColumnDirective field='Freight' headerText='Freight' width='100' format='C2' textAlign='Right' />
<ColumnDirective field='OrderDate' headerText='Order Date' format='yMd' width='100' />
</ColumnsDirective>
<Inject services={[Filter, Page]} />
</GridComponent>---
Filter Types Overview
Grid supports three filter UI types. Set via filterSettings.type property.
| Type | Description | Use Case |
|---|---|---|
| FilterBar (default) | Input fields below column headers | Real-time filtering as you type |
| Menu | Dropdown menu with operators in header | Precise operator selection |
| Excel | Checkbox list dialog | Multiple value selection |
| CheckBox | Checkbox list (Excel variant) | Categorical/distinct value filtering |
FilterBar Type (Default)
const filterSettings = { type: 'FilterBar' };
<GridComponent dataSource={data} allowFiltering={true} filterSettings={filterSettings} />Menu Type
const filterSettings = { type: 'Menu' };
<GridComponent dataSource={data} allowFiltering={true} filterSettings={filterSettings} />Excel/Checkbox Type
const filterSettings = { type: 'Excel' }; // or 'CheckBox'
<GridComponent dataSource={data} allowFiltering={true} filterSettings={filterSettings} />---
Disable Filtering for Columns
Set allowFiltering={false} on specific columns to hide filter bar/UI for those columns.
<GridComponent dataSource={data} allowFiltering={true}>
<ColumnsDirective>
<ColumnDirective field='OrderID' headerText='Order ID' width='100' />
<ColumnDirective
field='CustomerID'
headerText='Customer ID'
width='100'
allowFiltering={false} {/* No filter for this column */}
/>
<ColumnDirective field='Freight' headerText='Freight' width='100' />
</ColumnsDirective>
<Inject services={[Filter]} />
</GridComponent>Use This When:
- Column contains non-filterable data (images, custom templates)
- Column is action-only (buttons, commands)
- Column filters are not meaningful to users
---
Quick Reference
| Task | Property/Method | Example |
|---|---|---|
| Enable filtering | allowFiltering={true} | <Grid allowFiltering={true} /> |
| Set filter type | filterSettings={{ type: 'Menu' }} | Menu, FilterBar, Excel, CheckBox |
| Disable column filter | allowFiltering={false} | <ColumnDirective allowFiltering={false} /> |
| Filter programmatically | grid.filterByColumn() | grid.filterByColumn('OrderID', 'equal', 10248) |
| Clear filters | grid.clearFiltering() | grid.clearFiltering() |
| Inject module | <Inject services={[Filter]} /> | Required in <Inject> |
Related skills
How it compares
Choose syncfusion-react-grid when the project already licenses Syncfusion React; pick generic React table skills for open-source grid libraries.
FAQ
What does syncfusion-react-grid help developers implement?
syncfusion-react-grid helps developers implement Syncfusion's React Data Grid with sorting, filtering, paging, editing, and data binding. The skill covers column configuration, event handlers, and Syncfusion-specific grid APIs.
When should you use syncfusion-react-grid?
Developers should use syncfusion-react-grid when building React admin dashboards or data-heavy screens with Syncfusion's licensed component suite. It is not intended for TanStack Table or AG Grid projects.