Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/table-column-helpers-rsc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astryxdesign/core': patch
---

[fix] Table: proportional() and pixel() no longer throw when called from a React Server Component. The Table barrel carried a 'use client' directive that marked the pure column utilities as client functions; the directive now lives only on the component modules, and Table.doc.mjs documents which parts of the data-driven API are server-safe (#3457)
@arham766
2 changes: 2 additions & 0 deletions packages/core/src/Table/Table.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const docs = {
{ guidance: true, description: 'Use density and divider variants to match the information density and scanning needs of your data.' },
{ guidance: true, description: 'Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.' },
{ guidance: true, description: 'Set explicit width on every column using proportional() or pixel(). proportional(1) gives equal flex distribution with a 120px minimum that prevents columns from collapsing on narrow viewports. Omitting width skips the minimum.' },
{ guidance: true, description: 'Use the data-driven API from React Server Components: proportional(), pixel(), and column definitions without function props are server-safe. Columns using renderCell (or any function prop) need the table wrapped in a "use client" component, since functions cannot cross the server-client boundary.' },
{ guidance: false, description: 'Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.' },
{ guidance: false, description: 'Enable every plugin at once. Add only the features your use case requires to keep the interface focused.' },
{ guidance: false, description: 'Omit width on text-heavy columns; without an explicit proportional() width they have no minimum and can squish to near-zero on mobile.' },
Expand Down Expand Up @@ -165,6 +166,7 @@ export const docsDense = {
{ guidance: true, description: 'Use density and divider variants to match the information density and scanning needs of your data.' },
{ guidance: true, description: 'Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.' },
{ guidance: true, description: 'Set explicit width on every column via proportional() or pixel(). proportional(1) = equal flex w/ 120px min preventing collapse on narrow viewports. Omitting width skips the minimum.' },
{ guidance: true, description: 'Data-driven API is RSC-safe: proportional(), pixel(), column defs w/o function props work in Server Components. renderCell (any function prop) requires a "use client" wrapper.' },
{ guidance: false, description: 'Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.' },
{ guidance: false, description: 'Enable every plugin at once. Add only the features your use case requires to keep the interface focused.' },
{ guidance: false, description: 'Omit width on text-heavy columns; w/o explicit proportional() width they have no minimum and can squish to near-zero on mobile.' },
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/Table/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';
// No 'use client' here: this file only re-exports. Each client module below

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just removing use client is probably okay i don't think we need the comment

// carries its own directive, while the pure column utilities (proportional,
// pixel, generateColumns, paginateData) stay importable from React Server
// Components. A directive on this barrel would mark those helpers as client
// functions and make them throw when called during a server render (#3457).

/**
* @file index.ts
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/Table/rscBoundary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file Source invariants for the Table server-client boundary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this test is high value. Just making sure it works with a test plan is probably good enough for this fix!

*
* The Table barrel (index.ts) must stay free of 'use client' so the pure
* column utilities (proportional, pixel, generateColumns, paginateData)
* remain callable from React Server Components; a directive on the barrel
* marks them as client functions and they throw when called during a
* server render (#3457). The client boundary is carried by each component
* module instead, so this test pins both halves of that arrangement.
*/

import {describe, it, expect} from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import {fileURLToPath} from 'node:url';

const tableDir = path.dirname(fileURLToPath(import.meta.url));

function hasUseClient(file: string): boolean {
const content = fs.readFileSync(path.join(tableDir, file), 'utf-8');
return /^\s*['"]use client['"];\s*$/m.test(content);
}

describe('Table RSC boundary', () => {
it('keeps the barrel free of "use client" so column utilities stay server-safe', () => {
expect(hasUseClient('index.ts')).toBe(false);
expect(hasUseClient('columnUtils.ts')).toBe(false);
expect(hasUseClient('types.ts')).toBe(false);
});

it('keeps the directive on the client component modules', () => {
expect(hasUseClient('Table.tsx')).toBe(true);
expect(hasUseClient('BaseTable.tsx')).toBe(true);
expect(hasUseClient('TableCell.tsx')).toBe(true);
expect(hasUseClient('TableContext.ts')).toBe(true);
});
});
Loading