SPFx v1.13+ development: do it right from the start
Tags: javascript, beginners, react, tutorial
Published: 2022-02-05 09:37:50

Does the word really need yet another post on how to create SPFx solutions? For sure not, and it's not my goal here. But things are changing faster than the SPFx project generator, and tips&tricks are all over the place. 

This Quick Start focuses on SPFx v1.13+ projects, and references documentation (because who reads the manuals before diving head-first into development) and blogs to help you start right from the beginning.

## First things first
### Know your target
#### SPFx 1.13.1
Before [setting up](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment) your SharePoint Framework development environment, decide which **SharePoint and SPFx versions** you want to target. Here, I'm going to focus on the latest, SPFx v1.13.1.
[![SPFx 1.13.1 development environment compatibility](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5fmf8li3gdw362vg6hpd.png)<figcaption>SharePoint Framework development tools and libraries compatibility</figcaption>](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/compatibility#spfx-development-environment-compatibility)

#### Node Version Manager
Different SPFx versions require different node runtimes, and the easiest way to switch between them is to use **Node Version Manager**. This allows you installing and switching between multiple node.js and npm versions. 
Don't feel tempted to update to _npm v7_ just yet, it's still _not supported_.

#### Package Manager
If you target SPFx v1.5+, and want to speed installation up, you may use **pnpm**, which will save disk space and speed installation up. Have a look [here](https://www.voitanos.io/blog/npm-yarn-pnpm-which-package-manager-should-you-use-for-sharepoint-framework-projects/) for a comparison.

#### Wrap up:
[ ] Install nvm for [Windows](https://github.com/coreybutler/nvm-windows) or [unix, macOS, and windows WSL](https://github.com/nvm-sh/nvm)
[ ] [Install NodeJS on Windows](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows)
[ ] [Install pnpm](https://pnpm.io/installation#using-npm) 
[ ] [Set up](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment) your SharePoint Framework development environment

Quick check:
```powershell
> node --version
v14.15.0
> npm --version
6.14.8
> pnp --version 
6.30.0
> yo --version
4.3.0
```
We are good to go!

### Toolchain: good to know

#### @microsoft/generator-sharepoint vs @pnp/spfx

One of the npm packages included in the SharePoint Framework [toolchain](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/toolchain/sharepoint-framework-toolchain#sharepoint-framework-packages) is 
`@microsoft/generator-sharepoint`, a Yeoman plugin used to create new SPFx projects. 
You may also consider using `@pnp/spfx` which extends the Microsoft's generator with additional [features](https://pnp.github.io/generator-spfx/#features) (VueJS, anyone? Jest? Pipelines?).
- [@microsoft/generator-sharepoint](https://www.npmjs.com/package/@microsoft/generator-sharepoint) 
- [@pnp/spfx](https://pnp.github.io/generator-spfx/) 

#### @microsoft/sp-lodash-subset
Another important one is `@microsoft/sp-lodash-subset` which is a custom bundle of lodash. It includes only the most essential [functions](https://www.eliostruyf.com/utility-functions-install-extra-dependencies-sharepoint-framework/).

#### @microsoft/office-ui-fabric-react-bundle
`@microsoft/office-ui-fabric-react-bundle` is a custom bundle of `office-ui-fabric-react` that is optimized for SharePoint Framework. 

SPFx v1.13.1 is using "office-ui-fabric-react" v7. As of [version 8](https://github.com/microsoft/fluentui/wiki/Version-8-migration-guide#office-ui-fabric-react-to-fluentuireact), the `office-ui-fabric-react package` has been discontinued in favor of `@fluentui/react` but don't feel tempted to upgrade, it is recommended to use the Office UI Fabric React package included in by default.

## Scaffold a new project

### yo @microsoft/sharepoint options

Starting with SPFx v1.5, there's a support for additional package managers: PNPM and Yarn, with pnpm being my personal favorite:
```powershell
yo @microsoft/sharepoint --package-manager pnpm
```
When creating a monorepo, you may want to
```powershell
yo @microsoft/sharepoint --skip-install
```
to allow your monorepo build orchestrator to manage common node_modules folder and to create symlinks.

I'm going to create a [Quick Start](https://github.com/kkazala/SPFx-QuickStart) WebPart using React.

### ESLint as static analysis tool for TypeScript 

The generator is still using TSLint, which has been [deprecated](https://blog.palantir.com/tslint-in-2019-1a144c2317a9) since, like, forever. It will be eventually [updated](https://github.com/SharePoint/sp-dev-docs/issues/7692#issuecomment-1030035985), but for now we need to do the job ourselves. 
To replace it with ESLint, follow the [SharePoint Framework with ESLint](https://spblog.net/post/2020/12/22/sharepoint-framework-with-eslint) article. 
But wait, `gulp-eslint7` is already deprecated as well. Use `gulp-eslint-new` instead:

Remember to use **PNPM** :
```powershell
pnpm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin gulp-eslint-new eslint-plugin-react --save-dev
```

And just as Sergei mentioned in his post, you will receive an error after running `gulp build` for a WebPart using React: `error  Don't use ``{}`` as a type. ``{}`` actually means "any non-nullish value`.

Instead of changing {} to 'unknown', how about using [functional components](https://reactjs.org/docs/components-and-props.html) instead?

#### QuickStart.tsx
```typescript
export default function QuickStart(props: IQuickStartProps) {

    return (
      //...
              <p className={ styles.description }>{escape(props.description)}</p>
      //...
    );
}
```

Oh wait, but why am I still receiving tslint errors?

![tslint errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsv1h25d6s1i7bmgmhuu.png)

One of the packages comprising SharePoint Framework is `@microsoft/sp-tslint-rules` which defines custom tslint rules for usage with SharePoint client-side projects. They are still part of the solution, whilst tslint.json, disabling some of the rules, is not. 
I don't feel adventurous today, I'm restoring the tslint.json.

See what changed: [GitHub](https://github.com/kkazala/SPFx-QuickStart/commit/9fd44c130de520a7d41797288f74dcd7a61b2a4e).

### Developer information
Update **developer information** in the package-solution.json; if the WebPart will be used in Microsoft Teams, users will see this information when installing the app in Teams.

### Ain't time to lose
Tired of waiting for the `serve` command to complete, after each change you make? Use [spfx-fast-serve](https://github.com/s-KaiNet/spfx-fast-serve), _"a command line utility, which modifies your SharePoint Framework solution, so that it runs continuous serve command as fast as possible"_.

After installing the `spfx-fast-serve` globally, navigate to your solution's folder and run the following commands:
```powershell
spfx-fast-serve
pnpm install
```

If you are using ESLint instead of TSLInt, you need to add the following configuration:
#### fast-serve/config.json
```javascript
  "serve": {
    "eslint": true
  }
```

Now you can simply run `pnpm run serve` to start working with your solution. Make a change in your code and see how fast the page will refresh. What a difference!

> Remember, that SPFx 1.13+ does not support local workbench. See [Debugging SPFx 1.13+ solutions](https://dev.to/kkazala/debugging-spfx-113-solutions-11cd)

See what changed: [GitHub](https://github.com/kkazala/SPFx-QuickStart/commit/36aacbdf184218c0b89ded301ce16088a5025491).

### Error logging
Add error logging with [@pnp/logging](https://pnp.github.io/pnpjs/logging/). Define [Log level](https://pnp.github.io/pnpjs/logging/#log-levels) as a property to enable Verbose logging only when needed. 
```powershell
pnpm install @pnp/logging 
```

Errors returned by [@pnp/sp](https://pnp.github.io/pnpjs/sp/#pnpsp) commands are handled using `Logger.error(e)`, which parses and logs the error message. If the error message should be displayed in the UI, use custom errorhandler function implemented based on [Reading the Response](https://pnp.github.io/pnpjs/concepts/error-handling/#reading-the-response) example.
Users shouldn't see this setting, as it provides no value for them. Simply don't include it in the property pane.

#### QuickStartWebPart.ts
```typescript
import {
  ConsoleListener, Logger
} from "@pnp/logging";

export interface IQuickStartWebPartProps{
  logLevel?: number;
}
export default class QuickStartWebPartextends BaseListViewCommandSet<IQuickStartWebPartProps> {
  @override
  public onInit(): Promise<void> {
    Logger.subscribe(new ConsoleListener());
    if (this.properties.logLevel && this.properties.logLevel in [0, 1, 2, 3, 99]) {
      Logger.activeLogLevel = this.properties.logLevel;
    }
    Logger.write(`${LOG_SOURCE} Initialized QuickStartWebPart`);  
    return Promise.resolve();
  }
}
```

You may define the logLevel to error (3) in WebPart's manifest:
 
#### QuickStartWebPart.manifest.json
```javascript
{
  "preconfiguredEntries": [{
  //... 
    "properties": {
      "description": "QuickStart",
      "logLevel":3
    }
  }]
}
```
and set it to verbose (0) when troubleshooting is needed using `Set-PnPClientSideWebPart`.

```powershell
Set-PnPClientSideWebPart -Page Home -Identity xxx -PropertiesJson '"properties": {""description"": ""QuickStart"",""logLevel"":0}'
```

See what changed: [GitHub](https://github.com/kkazala/SPFx-QuickStart/commit/d246b15d31e75cba41d89af8975ad6f736b1a927)

In case you are using this approach with Custom Actions, you may define the logLevel using `Add-PnPCustomAction` command

```powershell
Add-PnPCustomAction -Title "Panel" -Name "panel" -Location "ClientSideExtension.ListViewCommandSet.CommandBar" -ClientSideComponentId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -ClientSideComponentProperties "{""logLevel"":""3""}" -RegistrationId 100 -RegistrationType List -Scope Web
```

Updating the [logLevel](https://pnp.github.io/pnpjs/logging/#log-levels) in an already deployed solution is done with:

```powershell
$ca=Get-PnPCustomAction -Scope Web -Identity "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ca.ClientSideComponentProperties="{""logLevel"":""1""}"
$ca.Update()
```

See [Working with modern client-side pages using PnP PowerShell](https://docs.microsoft.com/en-us/microsoft-365/community/working-with-modern-clientside-pages-using-pnp-powershell) for examples on how to obtain your WebPart's identity.

### Missing peer dependencies
If, like me, you are using pnpm, you have seen a lot of `✕ missing peer typescript@` during `pnpm install`:
![Missing peer dependencies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73bc4d7ovl1lum9hgv0k.png)
This is simply becasue pnpm is less forgiving, compared to npm or yarn. Once you add typescript to your devDependencies, the warning will dissapear:
```javascript
  "devDependencies": {
    //...
    "typescript": "^3.9.0"
  }
```

### Imports done right
When it comes to optimizing your solution, there's one low-hanging fruit: imports. 
Instead the whole library, reference only [specific ](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/toolchain/optimize-builds-for-production#reference-only-the-necessary-components) method(s) you want to use. 
And it it's going to be used only in specific cases, use [dynamic imports](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading#dynamic-import)

### Localizing
Localize your solution as described [here](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/localize-web-parts). You can get the `currentUICultureName` from ~~`spPageContextInfo`~~ `this.context.pageContext.legacyPageContext` property.

### Using different values in development and production?
Don't hard code, use this [approach instead](https://dev.to/kkazala/spfx-app-configuration-4d9i).

## Using React?

### Functional Components vs. Class Components
The React component created by the generator is using a class component: `React.Component`. Consider using [functional components](https://reactjs.org/docs/components-and-props.html) instead. It's cleaner, easier and use [hooks](https://reactjs.org/docs/hooks-intro.html).

And although there are [no plans](https://reactjs.org/docs/hooks-intro.html#gradual-adoption-strategy) to remove classes from React, even @fluentui/react v8 [converted](https://github.com/microsoft/fluentui/wiki/Version-8-migration-guide#function-component-conversions) some components from class components to function components. 

- [Understanding Functional Components vs. Class Components in React](https://www.twilio.com/blog/react-choose-functional-components). 

### Error Boundaries
Use [Error Boundaries](https://reactjs.org/docs/error-boundaries.html) to catch JavaScript errors in the component tree,  and to display a fallback UI.
There's already an `react-error-boundary` component you can [download](https://github.com/bvaughn/react-error-boundary#readme)

### Strict mode
Consider using [strict mode](https://reactjs.org/docs/strict-mode.html) for highlighting potential problems in an application. Strict mode checks are run in development mode only; they do not impact the production build.

### Fluent UI React

Use Fluent UI's [controls](https://developer.microsoft.com/en-us/fluentui#/controls/web) to build user interface, and [ThemeProvider](https://developer.microsoft.com/en-us/fluentui#/controls/web/themeprovider) to apply contextual theming.

To style specific component using colors from current theme:
```typescript
import { getTheme, Stack, IStackStyles} from "office-ui-fabric-react";
const theme = getTheme();

const stackStylesParent: IStackStyles = {
    root: {
      background: siteTheme.palette.themeLighter 
    },
};

export default function MyComponent(props: IMyComponentProps) {
  return <Stack styles={stackStylesParent} >
  </Stack >;
}

```


I realize this list could go on and on, but let me stop here.
Or did I forget something really important? Please share your tips in the comments below! :)