TNKS Data Table

Case Format Configuration

Configure the data table to work with different API naming conventions

Case Format Configuration

The DataTable component supports customizable case formatting for table keys, enabling seamless integration with different backend systems and naming conventions.

The Problem

Previously, the table component only supported snake_case formatting for API keys, which caused integration issues when working with APIs expecting camelCase or other formats. This led to data mismatches and errors when interfacing with different naming conventions.

Solution

The component now supports multiple case formats and custom key mappings:

  • snake_case: sort_by, page_size, created_at
  • camelCase: sortBy, pageSize, createdAt
  • PascalCase: SortBy, PageSize, CreatedAt
  • kebab-case: sort-by, page-size, created-at
  • Custom mappings: Define your own parameter names

Features

code

Built-in Formats

Support for snake_case, camelCase, PascalCase, and kebab-case out of the box

settings

Custom Mappings

Define your own parameter name mappings for non-standard APIs

split

Separate Formats

Use different formats for URL parameters and API requests independently

check

Backward Compatible

Existing implementations continue to work without changes

Basic Configuration

Full CamelCase API

Perfect for Express.js, Node.js, or TypeScript backends:

import { CaseFormatConfig } from "@/components/data-table/utils/case-utils";

const camelCaseConfig: CaseFormatConfig = {
  urlFormat: 'camelCase',  // URL: ?sortBy=name&pageSize=10
  apiFormat: 'camelCase',  // API: sortBy=name&pageSize=10
};

Full Snake_case API

Perfect for Python, Django, Ruby on Rails backends:

const snakeCaseConfig: CaseFormatConfig = {
  urlFormat: 'snake_case',  // URL: ?sort_by=name&page_size=10
  apiFormat: 'snake_case',  // API: sort_by=name&page_size=10
};

Mixed Formats (Default)

CamelCase in URLs, snake_case for API (default behavior):

const mixedConfig: CaseFormatConfig = {
  urlFormat: 'camelCase',  // URL: ?sortBy=name&pageSize=10
  apiFormat: 'snake_case', // API: sort_by=name&page_size=10
};

Custom Key Mapping

Map specific parameter names to match your API's requirements:

Laravel-Style API

const laravelConfig: CaseFormatConfig = {
  keyMapper: (key: string) => {
    const mappings: Record<string, string> = {
      'sortBy': 'sort',          // sortBy → sort
      'sortOrder': 'order',       // sortOrder → order
      'pageSize': 'per_page',     // pageSize → per_page
      'page': 'page',             // page → page (unchanged)
      'search': 'filter',         // search → filter
    };
    return mappings[key] || key;
  }
};

// API request will use:
// ?sort=name&order=asc&per_page=20&page=1&filter=john

GraphQL-Style API

const graphqlConfig: CaseFormatConfig = {
  keyMapper: (key: string) => {
    const mappings: Record<string, string> = {
      'sortBy': 'orderBy',
      'sortOrder': 'orderDirection',
      'pageSize': 'first',
      'page': 'offset',
    };
    return mappings[key] || key;
  }
};

Custom Business Logic

const customConfig: CaseFormatConfig = {
  keyMapper: (key: string) => {
    const mappings: Record<string, string> = {
      'sortBy': 'orderColumn',
      'sortOrder': 'direction',
      'pageSize': 'limit',
      'page': 'offset',
      'search': 'query',
      'from_date': 'startDate',
      'to_date': 'endDate',
    };
    return mappings[key] || key;
  }
};

Integration with DataTable

Step 1: Define Configuration

src/app/(section)/users/utils/config.ts
import { CaseFormatConfig } from "@/components/data-table/utils/case-utils";

export const tableCaseConfig: CaseFormatConfig = {
  apiFormat: 'camelCase',  // Match your backend
};

Step 2: Update Data Fetching Hook

src/app/(section)/users/utils/data-fetching.ts
import { DEFAULT_CASE_CONFIG } from "@/components/data-table/utils/case-utils";

export function useUsersData(
  page: number,
  pageSize: number,
  search: string,
  dateRange: { from_date: string; to_date: string },
  sortBy: string,
  sortOrder: string,
  caseConfig: CaseFormatConfig = DEFAULT_CASE_CONFIG
) {
  return useQuery({
    queryKey: [
      "users",
      page,
      pageSize,
      search,
      dateRange,
      sortBy,
      sortOrder,
      caseConfig
    ],
    queryFn: () => fetchUsers({
      page,
      limit: pageSize,
      search,
      from_date: dateRange.from_date,
      to_date: dateRange.to_date,
      sort_by: sortBy,
      sort_order: sortOrder,
      caseConfig,  // Pass configuration
    }),
  });
}

useUsersData.isQueryHook = true;

Step 3: Update API Function

src/api/user/fetch-users.ts
import { applyKeyMapping } from "@/components/data-table/utils/case-utils";

export async function fetchUsers(params, caseConfig = DEFAULT_CASE_CONFIG) {
  const queryParams = new URLSearchParams();

  // Apply case format to parameters
  const mappedParams = applyKeyMapping(params, caseConfig);

  Object.entries(mappedParams).forEach(([key, value]) => {
    if (value) queryParams.append(key, value.toString());
  });

  const response = await fetch(`/api/users?${queryParams.toString()}`);
  // ... rest of implementation
}

Complete Examples

Express.js / Node.js Backend

// Both URL and API use camelCase
const nodeConfig: CaseFormatConfig = {
  urlFormat: 'camelCase',
  apiFormat: 'camelCase',
};

// Results in:
// URL: /users?sortBy=name&sortOrder=asc&pageSize=20
// API: sortBy=name&sortOrder=asc&pageSize=20

Django / Python Backend

// URL uses camelCase, API uses snake_case
const djangoConfig: CaseFormatConfig = {
  urlFormat: 'camelCase',
  apiFormat: 'snake_case',
};

// Results in:
// URL: /users?sortBy=name&sortOrder=asc&pageSize=20
// API: sort_by=name&sort_order=asc&page_size=20

Laravel Backend

// Custom mappings for Laravel conventions
const laravelConfig: CaseFormatConfig = {
  apiFormat: 'snake_case',
  keyMapper: (key: string) => {
    const mappings: Record<string, string> = {
      'pageSize': 'per_page',
      'sortBy': 'sort',
      'sortOrder': 'order',
    };
    return mappings[key] || key;
  }
};

// Results in:
// API: sort=name&order=asc&per_page=20&page=1

Default Configuration

The default configuration maintains backward compatibility:

export const DEFAULT_CASE_CONFIG: Required<CaseFormatConfig> = {
  urlFormat: 'camelCase',
  apiFormat: 'snake_case',
  keyMapper: (key: string) => key, // No transformation
};

Live Example

Check the camelCase users example:

# Visit this route to see it in action
/example/users-camel-case

Migration Guide

Existing Projects

No changes required! The default configuration maintains current behavior.

New Projects with CamelCase API

Create Case Configuration

src/config/table-case-config.ts
import { CaseFormatConfig } from "@/components/data-table/utils/case-utils";

export const tableCaseConfig: CaseFormatConfig = {
  apiFormat: 'camelCase',
};

Update Data Fetching

Pass the configuration to your fetch functions:

export function useUsersData(...params) {
  return useQuery({
    queryKey: ["users", ...params, tableCaseConfig],
    queryFn: () => fetchUsers({ ...params, caseConfig: tableCaseConfig }),
  });
}

Update API Functions

Apply the configuration when building query parameters:

import { applyKeyMapping } from "@/components/data-table/utils/case-utils";

export async function fetchUsers(params, caseConfig) {
  const mapped = applyKeyMapping(params, caseConfig);
  // Use mapped parameters in API call
}

Type Definitions

Prop

Type

Benefits

  • Flexible Integration: Works with any backend naming convention
  • No UI Changes: Interface remains identical
  • Type Safety: Full TypeScript support
  • Backward Compatible: Existing code continues working
  • Developer Experience: Aligns with project conventions

Common API Patterns

BackendURL FormatAPI FormatCustom Mapping
Express.jscamelCasecamelCaseNone
DjangocamelCasesnake_caseNone
LaravelcamelCasesnake_casepageSizeper_page
Ruby on Railssnake_casesnake_caseNone
ASP.NETPascalCasePascalCaseNone
GraphQLcamelCasecamelCaseCustom resolvers

Next Steps

How is this guide?