React und Next.js
Die Komponenten über die generierten Wrapper in einer React- oder Next.js-Anwendung verwenden.
Das Paket liefert für jedes Element einen React-Wrapper unter
tuix-webcomponents/react, erzeugt mit
@lit/react. Die Wrapper nehmen die
Properties des Elements als Props, Objekte und Arrays gehen also unverändert
durch, und aus jedem Custom Event wird eine on*-Prop. Nichts muss von Hand
gewrappt werden.
React 18 rendert ein Custom Element, gibt aber jede Prop als String-Attribut weiter und kann keine Custom Events
empfangen: genau das lösen die Wrapper. React 19 beherrscht beides von Haus aus; die Wrapper lohnen sich dort
weiterhin für die typisierten Props und die on*-Event-Props.
1. Installieren
@lit/react und react sind Peer-Dependencies der Wrapper.
Die Design-Tokens einmal an der Wurzel der Anwendung einbinden. In
Next.js ist das app/layout.tsx:
Dieses Stylesheet holt auch Rubik vom Tuix-CDN, die Typografie ist damit abgedeckt. Wie sich die Schrift früher anfragen lässt, steht unter Theming.
2. Verwenden
Der Import aus tuix-webcomponents/react registriert die Custom Elements, mehr
ist nicht nötig. Die Props tragen die Property-Namen (iconRight, nicht
iconright):
Aus Sicht von React sind die Komponenten uncontrolled: sie melden eine Änderung und warten auf die neue Prop. Der Wert muss im Handler immer zurückgeschrieben werden, wie oben, sonst wirkt das Feld eingefroren.
Die Props sind aus den Properties des Elements typisiert, und diese Properties tragen die exportierten Enums als
Typ. Ein String-Literal ist deshalb nicht zuweisbar: variant={MainVariant.PRIMARY} kompiliert, variant="primary"
nicht. Die Enums stehen unter Enums und Typen.
Event-Props
Aus jedem Custom Event wird on plus der Name in PascalCase; ein Event, dessen
Name bereits mit on beginnt, behält ein einzelnes Präfix. Der Handler bekommt
das CustomEvent, typisiert mit seiner Nutzlast.
| Element | Prop | Event | event.detail |
|---|---|---|---|
tuix-button, tuix-icon-button | onCustomClick | customClick | Keine |
tuix-input | onChange, onBlur | on-change, on-blur | { value: string } |
tuix-input | onClick | on-click | Keine |
tuix-dropdown | onSelect | select | { value: string | number | boolean } |
tuix-dropdown | onInputSingle | input-single | { value: string } |
tuix-checkbox, tuix-toggle-button | onChange | change | { value: boolean, label?: string } |
tuix-radio-button | onChange | change | { value: string } |
tuix-table | onNextPage, onPreviousPage, onPageSizeChange | next-page, previous-page, page-size-change | { page: number, pageSize: number } |
tuix-toast, tuix-modal | onClose | on-close | Keine |
Neun dieser Props heissen wie eine React-DOM-Prop: onChange, onBlur und onClick an TuixInput, onSelect an
TuixDropdown, onChange an Checkbox, Radio-Gruppe und Toggle sowie onClose an Toast und Modal. An einem Wrapper
meint die Prop immer das Custom Event aus der Tabelle oben, nie das von React. onClick an einem TuixInput ist
das on-click des Elements, kein React-MouseEvent. Wo ein nativer Handler gebraucht wird, gehört er an ein
umschliessendes Element.
Der Wrapper heisst wie das Element selbst: TuixButton, TuixIcon,
TuixText, TuixPaper, TuixLink, TuixIconButton, TuixInput,
TuixDropdown, TuixCheckbox, TuixRadioButton, TuixToggleButton,
TuixTable, TuixToast, TuixModal.
3. Besonderheiten von Next.js
Die Seiten als Client-Code kennzeichnen. Custom Elements brauchen
ein DOM. 'use client' gehört an den Anfang jeder Komponente, die eines
rendert.
Server-Rendering ist unproblematisch. Der Server gibt
<tuix-button> samt Kindern aus; das Element upgraded im Browser und
schiebt die Kinder in seinen Slot. Nichts wird gemessen oder zufällig
erzeugt, die Hydration passt also.
Das Theme vor dem ersten Paint setzen, wenn die Anwendung einen Dark
Mode hat, damit die Property dark und die Seite von Anfang an
übereinstimmen:
Statischer Export (output: 'export') funktioniert. Importiert eine
Seite die Wrapper auf Modulebene und greift etwas darin auf window zu,
werden sie besser verzögert geladen:
Ohne die Wrapper
Für einen Einzelfall lässt sich das Tag direkt rendern und für TypeScript deklarieren:
Zu beachten: dieser Weg setzt Attribute, also nur kleingeschriebene Namen und weder Objekte noch Arrays noch Event-Handler.
Fehlersuche
| Symptom | Ursache |
|---|---|
ReferenceError: HTMLElement is not defined | Die Bibliothek wurde auf dem Server importiert. Import in eine Client-Komponente verschieben oder dynamic(..., { ssr: false }) |
Cannot find module '@lit/react' | @lit/react fehlt; es ist eine Peer-Dependency und wird neben dem Paket installiert |
Cannot find module 'tuix-webcomponents/react' | Der Resolver liest das Feld exports des Pakets nicht; TypeScript braucht moduleResolution: "bundler" |
Type '"primary"' is not assignable | Die Prop ist mit einem Enum typisiert. MainVariant.PRIMARY statt des Strings übergeben |
| Das Feld ändert sich nie | Der Wert wird im Change-Handler nicht zurückgeschrieben |
onCustomClick feuert nie | Es wird das Tag statt des Wrappers verwendet, React hat also keinen Listener registriert |
Eine Array- oder Objekt-Prop kommt als [object Object] an | Es wird das Tag statt des Wrappers verwendet, React setzt sie also als Attribut |
| Text erscheint in der falschen Schrift | Das Stylesheet mit den Design-Tokens ist nicht eingebunden, und es holt Rubik |
| Es rendert gar nichts | Weder die Wrapper noch die Bibliothek wurden importiert, das Tag also nie registriert |