carbon-design-system / carbon

A design system built by IBM
https://www.carbondesignsystem.com
Apache License 2.0
7.85k stars 1.81k forks source link

[Feature Request]: Use of React Server Components #14458

Open ghost opened 1 year ago

ghost commented 1 year ago

The problem

To benefit from the new full-stack Web applications architecture of React 18 / NextJS App Router, we need the presentational CDS components to be React Server Components.

The solution

Avoid using client-side functions (React use..., events, ...) in presentational components.

Examples

No response

Application/PAL

No response

Business priority

None

Available extra resources

No response

Code of Conduct

tay1orjones commented 1 year ago

@bjoanne Thanks for starting this discussion! A few questions:

  1. Could you list which components you think would be good candidates to be React Server Components?
  2. What benefits would you see from these components being React Server Components?

For the most part, CDS components are client components. This table from the NextJS docs illustrates this boundary.

image

Most teams we've heard of using NextJS 13 are wrapping our components in their own Client Component(s) for now until we add the "use client" directive.

tay1orjones commented 1 year ago

Composing a server component within children is also an option that most components support.

kiriny commented 1 year ago

...until we add the "use client" directive.

Is there a roadmap for this?

tay1orjones commented 1 year ago

...until we add the "use client" directive.

Is there a roadmap for this?

@kiriny Not right now. It's still a canary feature in React 18.2.0 and we'll probably wait until it's no longer experimental. This excerpt from the docs is relevant though:

Libraries published to npm should include 'use client' on exported React components that can be rendered with serializable props that use client-only React features, to allow those components to be imported and rendered by server components. Otherwise, users will need to wrap library components in their own 'use client' files which can be cumbersome and prevents the library from moving logic to the server later. When publishing prebundled files to npm, ensure that 'use client' source files end up in a bundle marked with 'use client', separate from any bundle containing exports that can be used directly on the server.

Many components in @carbon/react contain non-serializable props (functions, dom elements, date objects). In this case it sounds like we wouldn't want to assume use client as these can not be serialized.

dumaron commented 1 year ago

I'm very inexperienced with the carbon design system and what I'm gonna say might be partial or wrong. I think some of the components that carbon offers right now are not that far from being RSC-compatible. Most of the basic components related to forms, like Form or FormGroup, or basic layout components like FlexGrid cannot be considered server-side components because they rely on the usePrefix hook to compose the final class attribute:

export default function Form({ className, children, ...other }: FormProps) {
  const prefix = usePrefix();
  const classNames = classnames(`${prefix}--form`, className);
  return (
    <form className={classNames} {...other}>
      {children}
    </form>
  );
}

This split the broader topic into two sub-topics:

  1. For the people who want to create server-rendered pages with the Carbon design system aesthetic now, it is possible to fork the components and use them without the hook by just using the default cds value (or the preferred one). They'll lose the ability to have a context with the prefix, but I think would not come as a big loss. Ironically, it's closer to using vanilla carbon than the React version.
  2. For the library's future, it should be possible to expose some specific RSC-compatible versions of such basic components where the CSS prefix is implemented as an optional prop for those components. Again, this will make life harder for people who need to change the default prefix, and probably for the library maintainer since now they need to maintain two components. I hope we'll see some technique to replicate Context pattern also on server side components in the future 🤞

Generally speaking, I think having the possibility to define basic pages with standard HTML components with links, button, forms without having to pass a lot of javascript to the client is a nice feature that RSC enables for React users, and Carbon should start thinking into that with the goal of reducing client size and supporting React's attempt to progressively embrace standard HTML (as a React developer, it must be like the first time in 5 years that I use a real HTML form).

By supporting an RSC version of Link, Button, Form and related, Grid and related, Tile... and other components that offer limited interactions, it should be possible to create complete working websites that are server-side rendered only, like recreating the basic form-action example in this famous video.

dbrugger commented 1 year ago

It might work with making it a (server) context instead of a hook. Would also prefer that for components that are stateless.

dbrugger commented 1 year ago

@dumaron btw instead of fork a small babel function to replace usePrexix with the cds literal might be easier to work with than maintaining a fork

IRediTOTO commented 11 months ago

Hope we get upgrade soon for Nextjs 14

eharvey328 commented 1 month ago

bump as RCS has been released for a while now.

We are having to wrap all components in a use client:

'use client';

export * from '@carbon/react';

then import from this local file location instead.

Converting components to be static (where possible) improves the app performance and DX when using RSC. Nearly every component is incompatible because of the use of contexts-- particularly usePrefix.

A start could be to migrate usePrefix to a global variable instead.

// prefix.js
let prefix = 'cds';

export function setPrefix(newPrefix) {
  prefix = newPrefix;
}

export function getPrefix() {
  return prefix;
}
// Component.js
import { getPrefix } from './prefix.js'

export function Component() {
  const prefix = getPrefix();
}

Then consumers import the setPrefix into their app.


import { setPrefix } from '@carbon/react';

export default function RootLayout({ children }) {
  setPrefix('custom');

  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
tay1orjones commented 2 weeks ago

When adding use client to the components we'll need to be sure that it's applied correctly and not stripped by rollup https://github.com/rollup/rollup/issues/4699

When using a global/literal instead of context how would we solve for users being able to redefine the prefix for only a portion of their react tree? This is important for extending zoned/inline theming, versioning styles, and avoiding css conflicts in environments where styles are not well isolated between microservices. Removing support for this would be a breaking change.

I'd also be concerned about race conditions stemming from react not being able to trigger re-renders if the value changes. In concurrent rendering components might render multiple times before committing to the DOM, etc.

This is just one of the many system-wide issues we'd need to resolve. Many components rely on context for cross-component communication for features like focus management and may not work at all as a server component. Event handlers are everywhere, useId is required for unique ids for accessibility. Nearly all components use forwardRef for flexibility. There's many usages of other DOM APIs. It's a complex array of things to address, which I think is why there hasn't been any movement on this yet.