Hone logo
Hone
Problems

Interactive Data Table with Column Sorting in React

Build a reusable React component that displays tabular data and allows users to sort the data by clicking on column headers. This is a fundamental UI pattern for data visualization and manipulation, making data more accessible and understandable.

Problem Description

Your task is to create a React functional component called DataTable. This component should accept an array of data objects and an array of column definitions. Each column definition should specify the key of the data property to display, a human-readable header for the column, and potentially a data type for sorting purposes.

The DataTable component must render a table with:

  1. A header row where each cell corresponds to a column definition.
  2. A body with rows, where each row represents a data object.
  3. Cells within each body row displaying the value of the corresponding data property.

Crucially, the component must implement sorting functionality. When a user clicks on a column header, the table's data should be sorted based on the values in that column. Clicking the same header again should toggle the sort order (ascending to descending, and vice-versa).

Key Requirements:

  • Data Display: Render tabular data accurately.
  • Column Headers: Display user-friendly headers for each column.
  • Sorting: Implement client-side sorting for numerical, string, and date data types.
  • Sort Toggling: Allow users to switch between ascending and descending sort orders for a column.
  • Visual Feedback: Visually indicate which column is currently sorted and the direction of the sort (e.g., with an arrow).
  • Reusability: The component should be general enough to handle different datasets and column configurations.

Expected Behavior:

  • Initially, the table displays data in its original order.
  • Clicking a column header sorts the data by that column in ascending order.
  • Clicking the same column header again sorts the data in descending order.
  • Clicking a different column header sorts the data by the new column in ascending order.
  • The current sort column and direction should be visually indicated.

Edge Cases:

  • Empty data array.
  • Data objects with missing properties for a given column.
  • Mixed data types within a single column (handle gracefully, perhaps defaulting to string comparison if types are inconsistent).
  • Dates in various string formats (consider a common ISO format for simplicity in this challenge).

Examples

Let's define the structure for ColumnDefinition and RowData for clarity.

interface RowData {
  [key: string]: any;
}

interface ColumnDefinition<T extends RowData> {
  key: keyof T;
  header: string;
  dataType?: 'string' | 'number' | 'date'; // For sorting purposes
}

Example 1: Basic String and Number Sorting

Input Data:
[
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 3, name: 'Charlie', age: 35 }
]

Input Columns:
[
  { key: 'id', header: 'ID', dataType: 'number' },
  { key: 'name', header: 'Name', dataType: 'string' },
  { key: 'age', header: 'Age', dataType: 'number' }
]

Initial Render (Order may vary based on input array order):
| ID | Name    | Age |
|----|---------|-----|
| 1  | Alice   | 30  |
| 2  | Bob     | 25  |
| 3  | Charlie | 35  |

After clicking 'Age' header:
| ID | Name    | Age |
|----|---------|-----|
| 2  | Bob     | 25  |
| 1  | Alice   | 30  |
| 3  | Charlie | 35  |
(Indicate 'Age' is sorted ascending)

After clicking 'Age' header again:
| ID | Name    | Age |
|----|---------|-----|
| 3  | Charlie | 35  |
| 1  | Alice   | 30  |
| 2  | Bob     | 25  |
(Indicate 'Age' is sorted descending)

After clicking 'Name' header:
| ID | Name    | Age |
|----|---------|-----|
| 1  | Alice   | 30  |
| 2  | Bob     | 25  |
| 3  | Charlie | 35  |
(Indicate 'Name' is sorted ascending)

Example 2: Date Sorting

Input Data:
[
  { event: 'Meeting', date: '2023-10-26T10:00:00Z' },
  { event: 'Launch', date: '2023-11-01T14:30:00Z' },
  { event: 'Planning', date: '2023-10-20T09:00:00Z' }
]

Input Columns:
[
  { key: 'event', header: 'Event', dataType: 'string' },
  { key: 'date', header: 'Date', dataType: 'date' }
]

Initial Render:
| Event  | Date                |
|--------|---------------------|
| Meeting| 2023-10-26T10:00:00Z|
| Launch | 2023-11-01T14:30:00Z|
| Planning| 2023-10-20T09:00:00Z|

After clicking 'Date' header:
| Event  | Date                |
|--------|---------------------|
| Planning| 2023-10-20T09:00:00Z|
| Meeting| 2023-10-26T10:00:00Z|
| Launch | 2023-11-01T14:30:00Z|
(Indicate 'Date' is sorted ascending)

Example 3: Edge Case - Missing Data

Input Data:
[
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob', age: 25 },
  { id: 3, age: 35 }
]

Input Columns:
[
  { key: 'id', header: 'ID', dataType: 'number' },
  { key: 'name', header: 'Name', dataType: 'string' },
  { key: 'age', header: 'Age', dataType: 'number' }
]

Initial Render:
| ID | Name    | Age |
|----|---------|-----|
| 1  | Alice   |     |
| 2  | Bob     | 25  |
| 3  |         | 35  |

After clicking 'Age' header:
| ID | Name    | Age |
|----|---------|-----|
| 1  | Alice   |     |  // Missing values should ideally appear at the beginning or end consistently.
| 2  | Bob     | 25  |
| 3  |         | 35  |
(Indicate 'Age' is sorted ascending. Assume missing values are treated as the "smallest" or "largest" depending on sort direction).

Constraints

  • The data array will contain objects with consistent keys for the defined columns, though some values might be missing (e.g., undefined or null).
  • dataType will be one of 'string', 'number', or 'date'. Assume dates are provided in a format parsable by new Date() (e.g., ISO 8601).
  • The component should handle up to 1000 rows and 50 columns efficiently without noticeable lag.
  • All styling should be done using CSS or styled-components. Avoid inline styles for layout.

Notes

  • You'll need to manage the state of the sorted data and the current sort column/direction within the DataTable component.
  • Consider how to handle null or undefined values during sorting. A common approach is to place them at the beginning or end of the sorted list.
  • For dates, ensure you parse them into Date objects before comparison.
  • The visual indicator for sorting direction (e.g., an arrow) is a key part of the user experience.
  • Think about how to make the ColumnDefinition type generic to work with any RowData type.
Loading editor...
typescript