Skip to content

Commit 09ddbe8

Browse files
refactor: make DataTableProvider framework-agnostic by requiring tableIdentifier (calcom#24513)
* refactor: make DataTableProvider framework-agnostic by requiring tableIdentifier - Remove Next.js usePathname dependency from DataTableProvider - Make tableIdentifier a required prop instead of optional - Update all usages to provide explicit tableIdentifier values - This makes DataTableProvider usable in non-Next.js contexts Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: use usePathname at usage sites instead of hardcoding tableIdentifier - Add validation in DataTableProvider for empty/nullish tableIdentifier - Use usePathname() in Next.js apps to pass pathname as tableIdentifier - Use descriptive identifiers for non-Next.js package components - This keeps DataTableProvider framework-agnostic while allowing Next.js apps to use pathname Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * use pathname instead of hard-coded identifiers * change type of tableIdentifier * simplify --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 4ac8a51 commit 09ddbe8

14 files changed

Lines changed: 67 additions & 42 deletions

File tree

apps/web/modules/bookings/views/bookings-view.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,9 @@ function useSystemSegments(userId?: number) {
7474
export default function Bookings(props: BookingsProps) {
7575
const pathname = usePathname();
7676
const systemSegments = useSystemSegments(props.userId);
77+
if (!pathname) return null;
7778
return (
78-
<DataTableProvider
79-
useSegments={useSegments}
80-
systemSegments={systemSegments}
81-
tableIdentifier={pathname || undefined}>
79+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments} systemSegments={systemSegments}>
8280
<BookingsContent {...props} />
8381
</DataTableProvider>
8482
);

apps/web/modules/insights/insights-call-history-view.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { getCoreRowModel, getSortedRowModel, useReactTable, type ColumnDef } from "@tanstack/react-table";
4+
import { usePathname } from "next/navigation";
45
import { useMemo, useState, useReducer } from "react";
56

67
import {
@@ -62,8 +63,10 @@ function reducer(state: CallDetailsState, action: CallDetailsAction): CallDetail
6263
}
6364

6465
function CallHistoryTable(props: CallHistoryProps) {
66+
const pathname = usePathname();
67+
if (!pathname) return null;
6568
return (
66-
<DataTableProvider useSegments={useSegments} defaultPageSize={25}>
69+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments} defaultPageSize={25}>
6770
<CallHistoryContent {...props} />
6871
</DataTableProvider>
6972
);

apps/web/modules/insights/insights-routing-view.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import { usePathname } from "next/navigation";
4+
35
import { DataTableProvider } from "@calcom/features/data-table/DataTableProvider";
46
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
57
import {
@@ -13,9 +15,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
1315

1416
export default function InsightsRoutingFormResponsesPage() {
1517
const { t } = useLocale();
18+
const pathname = usePathname();
19+
20+
if (!pathname) return null;
1621

1722
return (
18-
<DataTableProvider useSegments={useSegments}>
23+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments}>
1924
<InsightsOrgTeamsProvider>
2025
<div className="mb-4 space-y-4">
2126
<RoutingFormResponsesTable />

apps/web/modules/insights/insights-view.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { usePathname } from "next/navigation";
34
import { useState, useCallback } from "react";
45

56
import {
@@ -39,8 +40,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
3940
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
4041

4142
export default function InsightsPage({ timeZone }: { timeZone: string }) {
43+
const pathname = usePathname();
44+
if (!pathname) return null;
4245
return (
43-
<DataTableProvider useSegments={useSegments} timeZone={timeZone}>
46+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments} timeZone={timeZone}>
4447
<InsightsOrgTeamsProvider>
4548
<InsightsPageContent />
4649
</InsightsOrgTeamsProvider>

packages/features/data-table/DataTableProvider.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
"use client";
22

33
import type { SortingState, OnChangeFn, VisibilityState, ColumnSizingState } from "@tanstack/react-table";
4-
// eslint-disable-next-line no-restricted-imports
54
import debounce from "lodash/debounce";
6-
// eslint-disable-next-line no-restricted-imports
75
import isEqual from "lodash/isEqual";
8-
import { usePathname } from "next/navigation";
96
import { useQueryState } from "nuqs";
107
import { useState, createContext, useCallback, useEffect, useRef, useMemo } from "react";
118

@@ -79,9 +76,9 @@ export type DataTableContextType = {
7976
export const DataTableContext = createContext<DataTableContextType | null>(null);
8077

8178
interface DataTableProviderProps {
82-
useSegments?: UseSegments;
83-
tableIdentifier?: string;
79+
tableIdentifier: string;
8480
children: React.ReactNode;
81+
useSegments?: UseSegments;
8582
ctaContainerClassName?: string;
8683
defaultPageSize?: number;
8784
segments?: FilterSegmentOutput[];
@@ -91,7 +88,7 @@ interface DataTableProviderProps {
9188
}
9289

9390
export function DataTableProvider({
94-
tableIdentifier: _tableIdentifier,
91+
tableIdentifier,
9592
children,
9693
useSegments = useSegmentsNoop,
9794
defaultPageSize = DEFAULT_PAGE_SIZE,
@@ -101,10 +98,8 @@ export function DataTableProvider({
10198
preferredSegmentId,
10299
systemSegments,
103100
}: DataTableProviderProps) {
104-
const pathname = usePathname() as string | null;
105-
const tableIdentifier = _tableIdentifier ?? pathname ?? undefined;
106-
if (!tableIdentifier) {
107-
throw new Error("tableIdentifier is required");
101+
if (!tableIdentifier.trim()) {
102+
throw new Error("tableIdentifier is required and cannot be empty");
108103
}
109104

110105
const filterToOpen = useRef<string | undefined>(undefined);

packages/features/data-table/GUIDE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,11 @@ function UserTable() {
145145
// ... other table options
146146
});
147147

148+
const pathname = usePathname();
149+
const tableIdentifier = "hard-coded idenfidier" // or pathname;
150+
148151
return (
149-
<DataTableProvider tableIdentifier="user-table">
152+
<DataTableProvider tableIdentifier={tableIdentifier}>
150153
<DataTableWrapper
151154
table={table}
152155
paginationMode="standard"
@@ -179,7 +182,7 @@ The context provider that manages all table state including filters, sorting, pa
179182

180183
```tsx
181184
interface DataTableProviderProps {
182-
tableIdentifier?: string; // Unique identifier for the table
185+
tableIdentifier: string; // Unique identifier for the table (throws if empty)
183186
children: React.ReactNode;
184187
useSegments?: UseSegments; // Custom segment hook
185188
defaultPageSize?: number; // Default: 10
@@ -530,7 +533,6 @@ Segments allow users to save and share filter configurations. There are two type
530533

531534
```tsx
532535
<DataTableProvider
533-
tableIdentifier="users"
534536
useSegments={useSegments} // Required to enable segments
535537
>
536538
{/* Your table content */}
@@ -564,7 +566,6 @@ const systemSegments: SystemFilterSegment[] = [
564566

565567
<DataTableProvider
566568
systemSegments={systemSegments}
567-
tableIdentifier="user-table"
568569
>
569570
{/* ... */}
570571
</DataTableProvider>
@@ -1210,7 +1211,7 @@ function UserTableContainer() {
12101211
const { data, isPending } = useUsers(filters);
12111212

12121213
return (
1213-
<DataTableProvider tableIdentifier="users">
1214+
<DataTableProvider>
12141215
<UserTable data={data} isPending={isPending} />
12151216
</DataTableProvider>
12161217
);

packages/features/ee/dsync/components/GroupTeamMappingTable.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ColumnDef } from "@tanstack/react-table";
22
import { useReactTable, getCoreRowModel } from "@tanstack/react-table";
3+
import { usePathname } from "next/navigation";
34
import { useRef, useState } from "react";
45

56
import { DataTableProvider } from "@calcom/features/data-table/DataTableProvider";
@@ -18,8 +19,10 @@ interface TeamGroupMapping {
1819
}
1920

2021
const GroupTeamMappingTable = () => {
22+
const pathname = usePathname();
23+
if (!pathname) return null;
2124
return (
22-
<DataTableProvider>
25+
<DataTableProvider tableIdentifier={pathname}>
2326
<GroupTeamMappingTableContent />
2427
</DataTableProvider>
2528
);

packages/features/ee/organizations/pages/settings/privacy.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import { usePathname } from "next/navigation";
4+
35
import { DataTableProvider } from "@calcom/features/data-table";
46
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
57
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
@@ -20,14 +22,16 @@ const PrivacyView = ({
2022
canDelete: boolean;
2123
};
2224
}) => {
25+
const pathname = usePathname();
2326
const { t } = useLocale();
2427
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery();
2528
const isInviteOpen = !currentOrg?.user.accepted;
26-
2729
const isDisabled = !permissions.canEdit || isInviteOpen;
2830

2931
if (!currentOrg) return null;
3032

33+
if (!pathname) return null;
34+
3135
return (
3236
<LicenseRequired>
3337
<div className="space-y-8">
@@ -45,7 +49,7 @@ const PrivacyView = ({
4549
<p className="text-muted text-sm">{t("manage_blocked_emails_and_domains")}</p>
4650
</div>
4751
<div className="mt-2">
48-
<DataTableProvider useSegments={useSegments} defaultPageSize={25}>
52+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments} defaultPageSize={25}>
4953
<BlocklistTable permissions={watchlistPermissions} />
5054
</DataTableProvider>
5155
</div>

packages/features/ee/teams/components/MemberList.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import classNames from "classnames";
1212
import { useSession } from "next-auth/react";
1313
import { signIn } from "next-auth/react";
14+
import { usePathname } from "next/navigation";
1415
import { useQueryState, parseAsBoolean } from "nuqs";
1516
import { useMemo, useReducer, useRef, useState } from "react";
1617
import type { Dispatch, SetStateAction } from "react";
@@ -166,8 +167,10 @@ interface Props {
166167
}
167168

168169
export default function MemberList(props: Props) {
170+
const pathname = usePathname();
171+
if (!pathname) return null;
169172
return (
170-
<DataTableProvider>
173+
<DataTableProvider tableIdentifier={pathname}>
171174
<MemberListContent {...props} />
172175
</DataTableProvider>
173176
);
@@ -668,14 +671,15 @@ function MemberListContent(props: Props) {
668671
getFacetedUniqueValues: (_, columnId) => () => {
669672
if (facetedTeamValues) {
670673
switch (columnId) {
671-
case "role":
674+
case "role": {
672675
// Include both traditional roles and PBAC custom roles
673676
const allRoles = facetedTeamValues.roles.map((role) => ({
674677
label: role.name,
675678
value: role.id,
676679
}));
677680

678681
return convertFacetedValuesToMap(allRoles);
682+
}
679683
default:
680684
return new Map();
681685
}
@@ -685,7 +689,7 @@ function MemberListContent(props: Props) {
685689
getRowId: (row) => `${row.id}`,
686690
});
687691

688-
const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({
692+
useFetchMoreOnBottomReached({
689693
tableContainerRef,
690694
hasNextPage,
691695
fetchNextPage,

packages/features/ee/workflows/components/VoiceSelectionDialog.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getCoreRowModel, getSortedRowModel, useReactTable, type ColumnDef } from "@tanstack/react-table";
2+
import { usePathname } from "next/navigation";
23
import { useMemo, useState } from "react";
34

45
import { DataTableProvider, DataTableWrapper } from "@calcom/features/data-table";
@@ -36,8 +37,10 @@ function VoiceSelectionTable({
3637
selectedVoiceId?: string;
3738
onVoiceSelect: (voiceId: string) => void;
3839
}) {
40+
const pathname = usePathname();
41+
if (!pathname) return null;
3942
return (
40-
<DataTableProvider useSegments={useSegments} defaultPageSize={1000}>
43+
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments} defaultPageSize={1000}>
4144
<VoiceSelectionContent selectedVoiceId={selectedVoiceId} onVoiceSelect={onVoiceSelect} />
4245
</DataTableProvider>
4346
);

0 commit comments

Comments
 (0)