Use Fluent UI Web Components with React
Fluent UI Web Components can be used in React applications. Let's take a look at how you can set up a project, starting from scratch.
Setting up the React project
First, you'll need to make sure that you have Node.js >= 8.2 and npm >= 5.6 installed. You can learn more and download that on the official site.
With Node.js installed, you can use create-react-app to create a new React project.
npx create-react-app fluent-app
Configuring packages
Next, we'll install the Fluent UI Web Component package, along with supporting FAST libraries. To do that, run this command from your new project folder:
npm install --save @fluentui/web-components @microsoft/fast-foundation @microsoft/fast-element @microsoft/fast-react-wrapper
Configure create-react-app
create-react-app ships with an eslint rule that makes working with Fluent UI Web Components difficult. There are two changes that will need to be made in the package.json
:
Set the EXTEND_ESLINT environment variable in start, build, and test scripts
{
//...
"scripts": {
"start": "EXTEND_ESLINT=true react-scripts start",
"build": "EXTEND_ESLINT=true react-scripts build",
"test": "EXTEND_ESLINT=true react-scripts test"
}
// ...
}
Override the eslintConfig
field to turn off the 'no-unused-expressions' rule
{
//..
"eslintConfig": {
"extends": "react-app",
"rules": {
"no-unused-expressions": "off"
}
}
//..
}
See configuring eslint for more information.
Using the components
With all the basic pieces in place, let's run our app in dev mode with npm start
. Right now, it displays the React logo and some editing instructions, since we haven't added any code or interesting HTML. Let's change that.
First, open your src/app.js
file and add the following code:
With all the basic pieces in place, let's run our app in dev mode with npm start
. Right now, it displays the React logo and some editing instructions, since we haven't added any code or interesting HTML. Let's change that.
First, open your src/app.js
file and add the following code:
import { provideFluentDesignSystem, fluentCard, fluentButton } from '@fluentui/web-components';
import { provideReactWrapper } from '@microsoft/fast-react-wrapper';
import React from 'react';
const { wrap } = provideReactWrapper(React, provideFluentDesignSystem());
export const FluentCard = wrap(fluentCard());
export const FluentButton = wrap(fluentButton());
This code uses the Fluent Design System to register the <fluent-card>
and <fluent-button>
components while automatically wrapping them into React components. Once you save, the dev server will rebuild and refresh your browser. However, you still won't see anything. To get some UI showing up, we need to write some HTML that uses our components. Replace the App component in your src/app.js
file with the following:
function App() {
return (
<FluentCard>
<h2>Fluent React</h2>
<FluentButton appearance="accent" onClick={() => console.log('clicked')}>
Click Me
</FluentButton>
</FluentCard>
);
}
To add a splash of style, add the following to the src/App.css
:
fluent-card {
padding: 16px;
display: flex;
flex-direction: column;
}
h2 {
font-size: var(--type-ramp-plus-5-font-size);
line-height: var(--type-ramp-plus-5-line-height);
}
fluent-card > fluent-button {
align-self: flex-end;
}
Congratulations! You're now set up to use Fluent and React!
Using the React Wrapper
Above, we leveraged the @microsoft/fast-react-wrapper
library to enable seamless integration of Fluent Components. There are a few additional ways to use this API for different web component scenarios.
Wrapping Design System Components
Previously, you've seen that we can wrap a Design System component by passing its registration function to the wrap
method as follows:
const { wrap } = provideReactWrapper(React, provideFluentDesignSystem());
export const FluentButton = wrap(fluentButton());
This code creates a wrapper that is configured with a React-compatible API and a Design System instance. When passing a Design System as the second parameter, you can then pass component registration functions to the wrap
helper. The helper will both register the web component with the Design System and wrap it in a type-safe React component, all with a single call.
Alternatively, you can skip providing the Design System to the wrapper, and use the generated registry to manually register all previously wrapped components.
const { wrap, registry } = provideReactWrapper(React);
export const FluentButton = wrap(fluentButton());
provideFluentDesignSystem().register(registry);
The final option is to handle everything by hand:
const { wrap } = provideReactWrapper(React);
export const FluentButton = wrap(fluentButton());
provideFluentDesignSystem().register(fluentButton());
Configuring Custom Events
If the wrapped component uses custom events that you intend to use from React, you will need to manually configure a mapping from React event name to the native event name. Here's an example of what that would look like if you wanted to leverage the FAST MenuItem's expanded-change
event:
const { wrap } = provideReactWrapper(React, provideFluentDesignSystem());
export const FluentMenuItem = wrap(fluentMenuItem(), {
events: {
onExpandedChange: 'expanded-change',
},
});
Additional Notes
create-react-app
FAST makes use of decorators to define components. At this time, create-react-app
does not support decorators. This won't be a problem when using components imported from FAST because they have already been transpiled by TypeScript - but to create components in a create-react-app
application you'll need to do one of the following:
- Define components without decorators
- Eject
create-react-app
and change Babel to support decorators - Use an intermediary like react-app-rewired
Data Binding
HTML Attributes
React is capable of rendering custom HTML elements and binding data to them, but it is beneficial to understand how React does this. React will apply all props to a custom HTML element as HTML attributes - including non-primitive types such as arrays and objects. Where some UI libraries provide binding syntaxes to distinguish setting properties, attributes, and events, React does not. This means that it can be very easy to end up with my-prop="[object Object]"
in your HTML. React is exploring solutions in this issue. See the section on interop layers for a work-around for this issue.
Custom events
React's synthetic eventing system comes with an unfortunate side-effect of being incapable of declaratively applying CustomEvent
listeners. Interop layers can be used to address this issue. Alternatively, a ref
can be used on the custom element to imperatively apply the event listener to the HTML element directly.
Interop layers: @skatejs/val and reactify-wc
@skatejs/val is a small library that wraps React's createElement
function and provides the ability direct React props explicitly to HTML attributes, DOM properties, or to declarative event listeners.
Another good option is reactify-wc. It provides similar capabilities as @skatejs/val
but does so by creating component wrappers.
TypeScript and TSX support
If you're using TypeScript, you'll need to augment the JSX.IntrinsicElements
interface to use custom elements in TSX. To do so, create a custom-elements.d.ts
file in your source directory and add the following:
// custom-elements.d.ts
declare namespace JSX {
interface IntrinsicElements {
/**
* React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> allows setting standard HTML attributes on the element
*/
'my-element': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
'my-attribute-name': string;
};
}
}