Skip to main content

Using React & Fluent UI in SPFx command set extensions

·3 mins

Why?

When you’re building SPFx command set extensions, you might’ve noticed that you don’t get a ton of UI options out of the box, just a plain dialog that can show some text, and a few buttons, this is fine for some scenarios, but if you want to build something a bit more advanced, you’ll no doubt have wanted to use React and Fluent UI, that’s what this guide is all about!

Getting started

When you’ve created your SPFx project, you’ll want to add the following packages a few packages, but their versions are important, so make sure you’re using the correct ones!

We’ll need these packages:

  • react
  • react-dom
  • @types/react
  • @types/react-dom

all in a matching version - the easiest way to determine which version you need is the SPFx Compatibility Matrix, check the react version for your SPFx version, and use that version for all the packages above.

So in my save I ran

npm i react@17.0.1 react-dom@17.0.1 --save-exact
npm i @types/react@17.0.1 @types/react-dom@17.0.1 --save-dev --save-exact

New I want to use Fluent UI, determining the version you need here is slightly more complicated, but if you’re on one of the (as of writing this) recent SPFx versions (1.16.0 and above ish) version 7 is the go to, this will change with SPFx 1.18.0 where we’ll be moving to version 8! at that time replace the @7 with @8 in the command below.

npm i @fluentui/react@7 --save-exact

Using React & Fluent UI

Now that we’ve setup all our dependencies, we can start building with them! - unlike when building a Web part we’ll need to rely on the dialog framework provided by Microsoft, this means you’ll be rendering your content inside a dialog, and not overtaking the entire page.

I wrote a ’neat’ component that’s reusable and allows you to render any React component inside a dialog (that’s extending the basedialog), this is what it looks like:

DialogReactRenderer.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { BaseDialog } from '@microsoft/sp-dialog';

export default class DialogReactRenderer<T> extends BaseDialog {
    private element: React.FunctionComponentElement<T>;

    constructor(element: React.FunctionComponentElement<T>) {
        super({ isBlocking: false });
        this.element = element;
    }

    public render(): void {
        ReactDOM.render(this.element, this.domElement);
    }

    protected onAfterClose(): void {
        super.onAfterClose();
        ReactDOM.unmountComponentAtNode(this.domElement);
    }
}

Now in your onExecute method you can render any component you want, for example, here I’m rendering a component called MainComp:


public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
    switch (event.itemId) {
      case 'COMMAND_1':
        const component = React.createElement(MainComp);
        const wrapper = new DialogReactRenderer(component);
        wrapper.show().catch(er => alert(er));
        break;
    }
}

the MainComp component just has to be a functional React component, mine just looks like this for now:

import * as React from 'react';
import { PrimaryButton } from '@fluentui/react';

export interface IMainCompProps { }

export const MainComp: React.FunctionComponent<IMainCompProps> = (props: React.PropsWithChildren<IMainCompProps>) => {
    const [count, setCount] = React.useState<number>(0);

    return (
        <>
            <div style={{ display: 'grid', placeItems: "center", height: 250 }}>

                <PrimaryButton onClick={() => setCount(count + 1)}>Click me ({count})</PrimaryButton>

            </div>
        </>
    );
};

See the full sample here.

TL;DR

Microsoft doesn’t ship a UI library with command set extensions, so unless you have other SPFx projects in your solution that’re using React & Fluent UI, you’ll have to add them yourself, this guide shows you how to do that, and how to render a React component inside a dialog.