Search code examples
reactjstypescriptsharepointtsx

How to import tsx into a SPFx extension


I have just started my first SharePoint project and cannot figure out how to use my React components in my extension. Here are the relevant files.

Navbar.tsx:

import * as React from "react";
export const Navbar = props => <div>Hello world</div>;

ReactSharePointNavbarApplicationCustomizer.tsx:

import { override } from "@microsoft/decorators";
import { Log } from "@microsoft/sp-core-library";
import {
  BaseApplicationCustomizer,
  PlaceholderContent,
  PlaceholderName
} from "@microsoft/sp-application-base";
import { Dialog } from "@microsoft/sp-dialog";

import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";

import styles from "./AppCustomizer.module.scss";
import { escape } from "@microsoft/sp-lodash-subset";

import * as Components from "./components";
import Navbar = Components.Navbar;

const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";

/**
 * If your command set uses the ClientSideComponentProperties JSON input,
 * it will be deserialized into the BaseExtension.properties object.
 * You can define an interface to describe it.
 */

export interface IReactSharePointNavbarApplicationCustomizerProperties {}

/** A Custom Action which can be run during execution of a Client Side Application */
export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
  IReactSharePointNavbarApplicationCustomizerProperties
> {
  private _onDispose(): void {
    console.log("No place holder.");
  }
  private _topPlaceholder: PlaceholderContent | undefined;
  private _renderPlaceHolders(): void {
    if (!this._topPlaceholder) {
      this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
        PlaceholderName.Top,
        { onDispose: this._onDispose }
      );

      if (!this._topPlaceholder) {
        return;
      }

      if (this.properties) {
        const Nav = Navbar(null);

        if (this._topPlaceholder.domElement) {
          this._topPlaceholder.domElement.innerHTML = `
            <div class="${styles.app}">
              <div class="ms-bgColor-themeDark ms-fontColor-white ${
                styles.top
              }">
               ${Nav}
               ${Navbar}
               <div>Hello</div>
              <Navbar/>
              </div>
            </div>`;
        }
      }
    }
  }

  @override
  public onInit(): Promise<void> {
    Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);

    // Added to handle possible changes on the existence of placeholders.
    this.context.placeholderProvider.changedEvent.add(
      this,
      this._renderPlaceHolders
    );

    // Call render method for generating the HTML elements.
    this._renderPlaceHolders();
    return Promise.resolve<void>();
  }
}

components:

export * from "./Navbar";

My goal is to use my react component as a navigation bar, however I cannot manage to combine tsx and ts in this context.

I followed this guide: https://learn.microsoft.com/en-us/sharepoint/dev/spfx/extensions/get-started/using-page-placeholder-with-extensions

Outside of these files, the only modifications I made were to add a components folder, with the component and index you see above.

Please help me solve this challenge.


Solution

  • After working on this for a few hours, I have found the solution. I was coming at this the wrong way, I needed to use ReactDOM to insert my TSX components. Afterward it was normal React development. No need to try to insert elements in some fancy way as I was doing before.

    Here is the working code.

    ReactSharePointNavbarApplicationCustomizer.ts:

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import { override } from "@microsoft/decorators";
    import { Log } from "@microsoft/sp-core-library";
    import {
      BaseApplicationCustomizer,
      PlaceholderContent,
      PlaceholderName
    } from "@microsoft/sp-application-base";
    
    import { Dialog } from "@microsoft/sp-dialog";
    
    import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";
    
    import styles from "./AppCustomizer.module.scss";
    import { escape } from "@microsoft/sp-lodash-subset";
    
    import Navbar, { INavbarProps } from "./components/Navbar";
    
    const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";
    
    export interface IReactSharePointNavbarApplicationCustomizerProperties {}
    
    export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
      IReactSharePointNavbarApplicationCustomizerProperties
    > {
      private _onDispose(): void {}
    
      private onRender(): void {
        const header: PlaceholderContent = this.context.placeholderProvider.tryCreateContent(
          PlaceholderName.Top,
          {
            onDispose: this._onDispose
          }
        );
        if (!header) {
          Log.error(LOG_SOURCE, new Error("Could not find placeholder PageHeader"));
          return;
        }
    
        const elem: React.ReactElement<INavbarProps> = React.createElement(Navbar);
        ReactDOM.render(elem, header.domElement);
      }
    
      @override
      public onInit(): Promise<void> {
        this.onRender();
        return Promise.resolve<void>();
      }
    }
    

    Navbar.tsx:

    import * as React from "react";
    import styles from "./Navbar.module.scss";
    
    import NavbarItem from "../NavbarItem";
    
    export interface INavbarProps {}
    
    export default class Navbar extends React.Component<INavbarProps> {
      constructor(props: INavbarProps) {
        super(props);
      }
    
      public render(): JSX.Element {
        return (
          <div className={"ms-bgColor-themeDark ms-fontColor-white " + styles.nav}>
            Hello world
          </div>
        );
      }
    }
    

    As you can see, the components.ts export file was unnecessary. And I am sure other code may still be useless in these examples.

    I found that importing tsx components into other tsx components works like normal React imports. Just import and insert as an element.