React
React does not play well with custom elements. While you can use custom elements with React, for some things you have to use the imperative ref-API. Basically, there are two issues when it comes to custom elements and React:
-
Properties vs. Attributes: custom elements distinguish between attribues and properties. Attributes are declarative but limited to string, number and boolean values. Properties are plain JavaScript and work for all types of values (including complex objects and functions) but can only be used imperatively. In React, everything is declarative. Under the hood, React always passes data as attributes to custom elements. This is OK for 90% of the time but if you have to pass data as JavaScript property, you have to use the imperative ref-API.
-
Events: React uses a synthetic event system while custom elements are using standard DOM events. For this reason, you cannot add a declarative listener to custom events. Instead, you have to invoke
addEventListener
on the instance of the custom element by hand.
You could solve both issues by using Refs. See the following example.
import React, {useRef, useEffect} from "react"
const MyReactComponent = () => {
const boxRef = useRef(null)
const textFieldRef = useRef(null)
useEffect(() => {
boxRef.current.addEventListener("zui-box-closed", () => {
console.log("Box was closed")
})
textFieldRef.current.validationCallback = (input) => {
// some validation logic
}
}, [boxRef, textFieldRef])
return (
<zui-box ref={boxRef} closable>
<zui-textfield ref={textFieldRef}></zui-textfield>
</zui-box>
)
}
React Support Library
While using refs works, it's not really intuitive for React developers. React developers want to use a declarative API and for this reason we provide an extra package @zeiss/zui-react
that contains React components for all ZUi Web Components. These React components are thin wrapper components that take care for the imperative handling of refs. They allow you to use ZUi Web Components in React without having to use refs by hand.
Setup
Install @zeiss/zui-react
and @zeiss/zui-react-icons
in your React application project:
npm install @zeiss/zui-react
npm install @zeiss/zui-react-icons
The initial setup of ZUi Web Components has to be done like described in Installation. Instead you can use ZUi React components like every other React component:
import React from "react"
import { ZuiBox, ZuiTextField } from "@zeiss/zui-react"
import { ZuiIconAlertsAlarmOn } from "@zeiss/zui-react-icons"
const MyReactComponent = () => (
<ZuiBox closable={true} onBoxClosed={() => console.log("Box was closed")}>
<ZuiIconAlertsAlarmOn />
<ZuiTextfield validationCallback={value => { // some validation logic}}/>
</ZuiBox>
)
Events
All ZUi react wrapper components in @zeiss/zui-react
allow you to add events similar to "normal" react components and standard HTML elements. There are some specialties, though.
For events that mimick standard DOM events (e.g. "click", "input", "change"), you can use onClick
, onInput
, onChange
ect.
ZUi custom events
Some of our components do support ZUi specific custom events, e.g. <zui-box>
fires zui-box-closed
when the close-button is pressed. All ZUi custom events are prefixed with "zui-" to prevent conflicts with other events. However, when using ZUi react wrapper components, you have to omit the prefix when adding event listeners:
// Wrong - this won't work
<ZuiBox onZuiBoxClosed={...}>
// Correct
<ZuiBox onBoxClosed={...}>
React specific special cases
React implements some special logic for some events.
For example, in plain react you can do <input onDoubleClick={() => console.log("double click")}/>
, but in reality, there is no such event named "double-click". The actual DOM event is named dblclick.
React itself brings some logic to allow you to add a listener to "dblclick" by invoking "onDoubleClick".
Our Zui react wrappers don't have this kind of special handling. They are only a thin layer on top of our actual web components. The only special handling is the differentiation between standard events (no -
in event name) and custom events as described above.
Therefore, for standard DOM events you always have to stick to the actual DOM event names:
<ZuiInput onDblclick={() => console.log("double click")}>
TypeScript
To get event type you can use GetZuiEvent
helper. Depending on the use case you might need to cast it first:
import type { GetZuiEvent } from "@zeiss/zui"
...
<ZuiMenubar
emphasis="selected"
variant="primary"
expandable
onMenubarSelected={(e: CustomEvent | Event) => handleClick(e as GetZuiEvent<MenubarElement, 'SelectedEvent'>)}
>
const handleClick = (event: GetZuiEvent<MenubarElement, 'SelectedEvent'>): void => {
if (event.detail && event.detail.value) {
console.log(event.detail.value);
}
};
Testing with JSDOM
If you are using JSDOM as a test environment (i.e. you are using create-react-app to bootstrap your project), tests involving ZUi React components won't run, because JSDOM is not a real browser and thus a lot of browser / Web related APIs are missing, that are required for using the ZUi Web Components.
We advise to setup a e2e environment running in a real browser. ZUi web components are developed for the web platform and thus should be tested on it, instead on being tested on a faked web environment.
However for simple unit test you can use the supplied @zeiss/zui-react/tools/jsdom.fixes.js
to mock the currently used browser APIs:
// in src/setupTests.js or src/setupTests.ts
import '@zeiss/zui-react/tools/jsdom.fixes.js';
N.B. You will need a recent (>16
) JSDOM version. If you are using create-react-app, you will need to use at least version 4.*
to get a recent JSDOM integration automatically set up.
We try to keep those mocks up to date with the APIs needed for ZUi web components.
Data Attributes
HTML data-*
attributes are supported starting with @zeiss/zui-react@2.9.0
.
Notice: Data attributes and other ZUi-related attributes/props like emphasis
or value
are assigned at different points in time in the render cycle of the component.
- data attributes will be assigned immediately in the first render cycle of react
- All other attributes/props are assigned imperatively in a
useEffect
that's executed after the initial rendering
For testing purposes this means that you should be able to access all ZUi elements immediately in your test via a data-test-id
but in some cases may need to await another render cycle until the other props are passed to and handled by the ZUi element.
React Example App
There is a small example app that uses ZUi-Web components in React. In this app, components are used in combination with each other and not each one separately as in the storybook. The goal is, among other things, to spot bugs resulting from the interaction of components, which is hard or impossible to find when looking at components in isolation. In addition, the example app gives an idea of how to integrate ZUi Web in React. The application doesn't implement a real world use-case but is just a showcase without specific logic.
The react example app is part of the mono-repo of ZUi web.
To access the repository you need access to our Azure DevOps project, which is the same access as to our ZUi public issue board. If you do not have the rights please contact us.