Build a better technical architecture with reusable components in React for SharePoint Framework webparts

If there is a complex web part to be implemented (with over 5000 lines of code), then the important question to ask is how to distribute the controls logic, so it could be better maintained. From a technical architecture point of view, better readability and efficiency, the react component controls provides a suitable solution for it.

Another important consideration for this is to increase performance of the control during build and debugging. From experience, if a control’s logic goes beyond 5000 lines of code then the build performance decreases drastically, it takes about 30 secs or more to build and another 30 secs or more to ready the control for debugging.

Thus, the important question is how to plan and implement it. In this blog, we will look at these topics.

Scoping and Implementation plan:

It is advisable to start planning for it as soon as possible before the dev cycle so to prevent any rework. Generally start with the solution planning steps at start of a project. The most important point is to define and divide the requirements into independent and dependant control requirements. An independent control will allow the logic to be reused across other components and increase efficiency.

From a performance point of view, if there is more than 5000 lines of code and more than 20 controls in the page, the gulp compile and serve takes too long, generally 30 secs or more, my patience runs out in like secs ūüôā

For scoping and implementation, consider the below.

1. Think controls not pages – think of controls like reusable functions which could be reused in many pages, so the functionality can be replicated.

2. Divide business logic into isolated independent components – design the components with reusability in mind but isolated from each other in functionality to provide less dependancy code.

3. Share information across controls using properties – use properties of React components to send information to the controls

4. Identify isolated components in a page – determine the components that could be created for the page providing flexibility and ease of build. If there are too many components then switching between components might add to page performance.

5. Allow passing styling and context information to the sub component¬†– a very important consideration for the sub component is to allow the styling the child component. Since the sub component might have it’s own control, the best way to allow styling is to pass a class or styling information to the sub component.

6. Get data out of the child control – after the child control has performed it’s requirement, the result(s) needs to be passed to the master component so it could be reused. This could be done by method override to pass values out of the control.

Implementation Steps

Before starting the implementation, it is important to plan on the above scoping points and divide the master component into isolated components that will have their own logic and page lifecycle methods. Next, is to implement the individual component logic and then finally embed the child control into the master component.

  1. Divide the logic of master component into sub-components that could be build into separate files
  2. Create .tsx files for each of the component. Add properties in the sub component to receive data from the master component. The subcomponent will act as any other component when built.
    Note: Pass the page context from Master to child in case if you are using SharePoint list or libraries in the component.
  3. Use page life cycle method to initialize the sub-component and values. For more information, please check the blog here.
  4. In order to pass values outside the child control, use an export method and override that method in the master component.
  5. After the child component is created, now add the control into the master control in the render method. Pass any value to the subcomponent using the properties.
  6. Pass styling information to the sub component as needed.
  7. In order to get values from the child control, override a method from Child component and set the output values to local values of the master component
  8. After the above steps are done, we will have a child component implemented which could be reused across multiple master components

Below is the example code for Child and Master Components for reference purposes.

Child Component

import * as React from 'react';
import { ChildComponentState } from './ChildComponentState';
import { WebPartContext } from '@microsoft/sp-webpart-base';
export interface ChildComponentProps
{
context : WebPartContext;
childoutput?: (item: any) => void;
}
export default class ChildComponent extends React.Component<ChildComponentProps, ChildComponentState> {
constructor()
{
super();
this.state = {
stateprop1 : "",
stateprop2 : this.props.prop1
};
}
public componentWillMount()
{
}
/**
* Any event method to set the value
* @param item
*/
private _onItemChange = (item: any) => {
const { childoutput } = this.props;
if (childoutput) {
childoutput(item);
}
}
public render(): React.ReactElement<ChildComponentProps> {
return (
<div> .. </div>
)};
}

Master Component

export default class MasterComponent extends React.Component<MasterComponentProps, MasterComponentState> {
constructor()
{
super();
this._childSelect = this._childSelect.bind(this);
this.state= {
stateprop1 : null,
stateprop2 : true
}
}
public componentWillMount()
{
}
/**
* Get the value from the child control
* @param item
*/
private _childSelect(item : any)
{
this.setState({
selectObj : item
});
}
public render(): React.ReactElement<MasterComponentProps> {
return (
<div>
<div className="ms-Grid" dir="ltr">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm12 ms-md12 ms-lg12">
{/* This is a child control. Pass properties and get output values*/}
<ChildComponent context={this.props.context} childoutput={this._childSelect}> </ChildComponent>
</div>
</div>
</div>
</div>
);
}

Conclusion

In this blog we saw, how we could reuse or divide component logic across multiple components using Master and Child component logic as described above. Even it might take some time setting this up but the greatest advantage is reusability and performance of the code that will benefit long term in managing the code better.

Happy Coding!!!

 

When and how to use React component lifecycle management in a SharePoint Framework?

There are various advantages of using React lifecycle methods in building SharePoint Framework components. It is not a necessity to use component lifecycle methods but we could get a lot out by using these methods with states. For a generic understanding of React component lifecycle methods, check here

Note: The use-cases description in this blog are specific to the SharePoint Framework lifecycle but could be considered for app using React in App model too.
The details in this blog are for guidance and can vary based on the requirements, so please use your best judgement while implementing the lifecycle methods

Few of the benefits of using React component lifecycle methods are :

1. Distribute any data pull workloads between initial load and component updates cycle. Not all data need to be loaded during the render method call. We could break the component load into various cycles.

2. Identify changes and implement code based on property or state changes.

3. Isolate any component load issues into various stages

React Component Lifecycle methods:

1. OnInit method – This is the start method of the web part. It is suggested not to do much here, unless it is required by the webpart life cycle.

// SPFx context init below
public onInit(): Promise<void> {
return super.onInit().then(_ => {
// other init code may be present
sp.setup({
spfxContext: this.context
});
});
}

view raw
React OnInit
hosted with ❤ by GitHub

Use Case: Mostly this method could be used initiate environment level variables or global variables such as spcontext of PnPjs library.

2. ComponentWillMount – This method is called before the render method every time the control is loaded or refreshed.

Use cases: The code related to initial data pull for controls could be placed in this method. This is where the data feeds into required for dependency driven controls for eg. Web service and REST calls. It is advisable to show a loading message during this function run.

// Do a REST Query to get data from REST using spHttpClient
public componentWillMount()
{
const items: any = await this.props.context.spHttpClient.get(restApi, SPHttpClient.configurations.v1, {
headers: {
'Accept': 'application/json;odata.metadata=none'
}
}).then(resp => resp.json());
}

3. ComponentDidMount – This method is called after the render method every time the control is loaded or refreshed. The code related to pre-population of controls could be placed here.

Use cases: This method is mostly used to load the state of the controls. Since React code is a state driven Framework, any state changes updates the controls with new changes but doing it here allows to set default values if needed before the state is updated for long running business rules.

// Using pnpjs to get items and
public componentDidMount()
{
sp.web.lists.getByTitle(listTitle).items.get().then((items: Item[]) => {
this.setState({
stateValue : items
});
});
}

4. ComponentDidUpdate – This method is called every time a Property or State is updated. Based on the update and if anything has changed, the implementation code could be run.

Use cases: In this method, we could check if a property or state has changed and act on the change.

With regards to state, it is beneficial when you would like to only make further REST calls or complex business logic when key fields are updated.

With regards to property changes it is beneficial when you would like to reload the state of the component based on this change.

Note: There are other life cycle methods in React which do the same but they might be deprecating soon.

// Check the component properties and state for changes
public componentDidUpdate(prevProps : IProps, prevState : IState) : void
{
// If properties have changed bind it
if(this.props.properties !== prevProps.properties || this.state.stateValue !== prevState.stateValue)
{
//Do more things here
}
}

5. Render method : This method is called to render the final HTML based on state change. We can include some switch logic or call html rendering functions.

Use cases: The use case is pretty simple for this one, reneder all the HTML on to the page.

public render(): React.ReactElement<IProps> {
return (
<div>
{
this.props.properties ? (
<TooltipHost content={this.props.tooltipMessage || strings.message}
id='pntp'
calloutProps={{ gapSpace: 0 }}
directionalHint={this.props.tooltipDirectional || DirectionalHint.leftTopEdge}>
{componentHTML}
</TooltipHost>
) : (
<div>
{componentHTML}
</div>
)
}
</div>
)
}

view raw
React renderMethod
hosted with ❤ by GitHub

Conclusion

In this blog, we have looked at some of the use cases of using life cycle methods of React components to distribute the web part functionality across various life cycle methods.

Options to consider for SharePoint Framework solutions deployment

There are various options to package and deploy a SPFx solution and as part of packaging and deployment process, the devs have to identify a best approach for their team. Sometimes it becomes a nightmare to plan the right approach for your solution, if you haven’t weighed the options properly.

Working at multiple implementations of SPFx solution for sometime now, I have been able to get an idea of various options and approach for them. Hence in this blog, we will at these options and look at merits and limitations for each.

At a high level, below are the main components that are deployed as part of SPFx package deployment:

1. The minified js file for all code

2. The webpart manifest file

3. Webpart compiled file

4. The package (.pckg) file with all package information

Deployment Options

Please check this blog for an overview of the steps for building and packaging a SPFx solution. The packaged solution (.sppkg) file can then be deployed to a App catalog site. The assets of the package (point 1-3 of above) could be deployed by any of the four below options. We will look at the merits and limitations for each.

1. Deploy to Azure CDN 

The assets could be deployed to an Azure CDN. The deployment script is already a part of SPFx solution and could be done from within the solution. More detailed steps for setting this up are here.

Note: Please remember to enable CORS on the storage account before deployment of the package. 
If CORS is not enabled before CDN profile is used, you might have delete and recreate the Storage account.

Merits:

  • Easy deployment using gulp
  • Faster access to assets and web part resources because of CDN hosting
  • Add geographical restrictions (if needed)

Limitations:

  • Dependency on Azure Subscription
  • Proper set up steps required for setting up Azure CDN. In some cases if the CDN if not set properly, then the deployment has to be done again.
  • Since the assets are deployed to a CDN endpoint, so if assets need restricted access then this mayn’t be recommended

2. Deploy to Office 365 Public CDN

For this option, you will need to enable and set up Office 365 CDN in your tenancy before deployment. For more details of setting this up, check the link here.

Merits:

  • Faster access to assets and web part resources because of CDN hosting
  • No Azure subscription requirement
  • Content is accessible from SharePoint Interface

Limitations:

  • Manual copy of assets files to CDN enabled SharePoint library
  • Office 365 CDN is a tenant setting and has to be enabled for the whole tenancy
  • Since the assets are deployed to a CDN endpoint, so if assets needs restricted access then this mayn’t be recommended
  • Accidental deletion could cause issues

3. Deploy to SharePoint document library

This is also an option to copy for the compiled assets to be copied to a SharePoint document library anywhere in the tenancy. Setting this up is quite simple, first set the setting “includeClientSideAssets”: false in package-solution.json file¬†and then set the CDN details in write-manifests.json ¬†file.

Merits:

  • No need of additional Azure hosting or enabling Office 365 CDN
  • Access to Assets files from SharePoint interface

Limitations:

  • Manual Copy of assets file to SharePoint library
  • Accidental deletion could cause issues

3. Include as part of Solution Package (Deploy to ClientAssets in App Catalog)

From SPFx version 1.4, it is possible to include assets as part of the package file and deploy it to the hidden ClientAssets library residing in App Catalog. It is set in the package-solution.json file¬†“includeClientSideAssets”: true.

Merits:

  • No extra steps needed to package and deploy assets

Limitations:

  • Increases the payload of the package file
  • Risk for Tenant admins to deploy script files to the Tenant App Catalog.

Conclusion

In this blog, we saw the various options for SPFx deployment, and merits and limitations of each approach.

Happy Coding !!!

Programmatically deploy and add SharePoint Framework Extensions using SharePoint CSOM and PowerShell

In the previous blog here, we looked at how to deploy and install SharePoint Apps. Now let’s look at installing SharePoint Framework extensions – Listview command sets programmatically.

SharePoint CSOM

SharePoint Framework has three type of extensions that could be created – Application customiser, Listview command sets and Field customisers. In this blog, we will look at adding list view command sets programmatically.

Listview command extensions are actually custom actions installed in a library or list. Hence to activate it we will go to the library/list, find the installed custom actions, if not installed we will install the new custom action. Below is the code for that.

private string appJsonInfo;
private AppDeclaration Apps;
// A class to hold JSON converted objects for Apps
private class AppDeclaration
{
public string appName;
public string appId;
public string clientsideId;
}
// Declare and create a Json Object for all Apps
public void DeclareAppIds
{
appJsonInfo = "[{appId: \"xxxxx-xxxx-xxxx-xxxx-xxxxxxxx\", appName: \"testApp\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx\"}, " +
"{appId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx\", appName: \"testApp1\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxxx-xxxxxx\"}, " +
"{appId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx\", appName: \"testApp2\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxxx-xxxxxx\"}]";
Apps = JsonConvert.DeserializeObject<List<AppDeclaration>>(appJsonInfo);
}
public void AddExtensions(string listURL)
{
AppDeclaration App = appConfiguration.Apps.Find(app => app.appName.Contains("<app name>"));
Guid AppID = new Guid(App.clientsideId);
string AppName = App.appName;
using (ClientContext context = new ClientContext(<siteUrl>))
{
if (AppID != Guid.Empty)
{
context.Credentials = new SharePointOnlineCredentials(UserName, SecurePass);
List list = context.Web.GetListByUrl(<list URL>);
log.Info("Finding and Adding Extenstion to " + list.Title);
context.Load(list);
context.Load(list, t => t.UserCustomActions);
context.ExecuteQuery();
UserCustomActionCollection listExt = list.UserCustomActions;
bool AppPresent = false;
foreach (UserCustomAction customAcc in listExt)
{
if (customAcc.ClientSideComponentId == AppID)
{
tenancyAppPresent = true;
continue;
}
}
if (!tenancyAppPresent)
{
UserCustomAction customAction = list.UserCustomActions.Add();
customAction.ClientSideComponentId = AppID;
customAction.Location = "ClientSideExtension.ListViewCommandSet";
customAction.Name = AppName;
customAction.Title = AppName;
customAction.Update();
context.ExecuteQuery();
}
}
else
throw new Exception("System Error – App cannot be added as no App ID found");
}
}

PowerShell

We could also use PnP PowerShell to add the Library extension onto a page using the code below

$credentials = Get-Credential
Connect-PnPOnline "https://<your-tenant&gt;.sharepoint.com/sites/<target-site>" -Credentials $credentials
## You can use the following PnP cmdlet
## ClientSideComponentId from Manifest.Json of the extension
Add-PnPCustomAction -Name "<CommandSetName>" -Title "<CommandSetTitle>" -Description "<Text>" -RegistrationId "101" -RegistrationType List -ClientSideComponentId "<Id from Manifest.json>" -Location "ClientSideExtension.ListViewCommandSet"

Hence, above we saw how we could add extensions onto a library or list using CSOM or PowerShell

Happy Coding!!

Implementing Bootstrap and Font-awesome in SharePoint Framework webparts using React

Responsive Design has been a biggest driving factor for SharePoint framework (SPFx) solutions. In a current SPFx project using React, we are building a component with SharePoint framework that uses bootstrap elements and font-awesome icons for responsive look and feel. While building the UI piece, we resolved many issues during the initial set up, so writing this blog with detail steps for reference. One of the key fixes mentioned in this post, is for the WOFF2 font-type file which is a component in font-awesome and bootstrap.

In this blog post, I will not be detailing the technical implementation (business logic and functionality) bit but only the UI bit. The functionality bit will be part of another post. So here we go..

Steps:

  1. Create a SharePoint Framework project using Yeoman. Refer this link from Microsoft docs for reference.
  2. Next, install JQuery, Bootstrap and Font-awesome using npm so that it can be available from reference from within node_modules (why jQuery? because it is needed for some of the other functionality, not related to bootstrap directly)
    npm install jquery --save
    npm install @types/jquery --save-dev
    npm install bootstrap --save
    npm install @types/bootstrap --save-dev
    npm install jquery --save
    npm install @types/jquery --save-dev
    

    Check the node_modules folder to make sure they are installed successfully

  3. Now locate config.json file in config folder and add the below bit for third party JS library references.
    "externals": {
          "jquery": {
          "path": "node_modules/jquery/dist/jquery.min.js",
          "globalName": "jQuery"
        },
        "bootstrap": {
          "path": "node_modules/bootstrap/dist/js/bootstrap.min.js",
          "globalName": "bootstrap"
        }

    Then reference them in .ts file in the src folder using import

    import * as jQuery from "jquery";
    import * as bootstrap from "bootstrap";
    
  4. For CSS reference, we can either refer to the public CDN links using SPComponentloader.loadCss() or else refer to the local version as below in the .tsx file
    Note: Don’t use ‘require’ for js scripts as you have already imported them in above step. If included again it will cause a component load error.

    require('../../../../node_modules/bootstrap/dist/css/bootstrap.css');
    require('../../../../node_modules/bootstrap/dist/css/bootstrap.min.css');
    require('../../../../node_modules/bootstrap/dist/css/bootstrap-theme.css');
    require('../../../../node_modules/bootstrap/dist/css/bootstrap-theme.min.css');
    require('../../../../node_modules/font-awesome/css/font-awesome.css');
    require('../../../../node_modules/font-awesome/css/font-awesome.min.css');
    
  5. When using React, copy your html to the .tsx file in components folder. If you want to use the HTML CSS classes as-is and not the SASS way, refer to this blog post. For image references, here is a good post to refer.For anyone new to React as me, few tips below for styling:
    1. Use className instead of HTML class attribute
    2. In order to use inline styles, use style={{style attributes}} or define an object, since everything in JSX are elements
  6. When ready, use gulp serve to launch your solution in local workbench.
    Important: If¬†you’re using custom icons or fonts from above CSS libraries, you will receive Typescript errors saying that loader module was not found for WOFF2 font type. So to resolve that we will need to push the custom loader for WOFF2 font type through gulpfile.js as below.First install url-loader from npm.

     npm install url-loader --save-dev

    Then modify gulpfile.js at the root directory to load the custom loader.

    build.configureWebpack.mergeConfig({ 
      additionalConfiguration: (generatedConfiguration) => { 
        generatedConfiguration.module.loaders.push([ 
          { test: /\.woff2(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader', query: { limit: 10000, mimetype: 'application/font-woff2'} } 
        ]); 
        return generatedConfiguration; 
      } 
    });
    

    Now gulp serve your solution and it should work fine.

You might still face CSS issues in the solution where that doesn’t exactly match the HTML-CSS implementation. To resolve any conflicts, use CSS Override (!important) ¬†wherever necessary.

In this post I have shown how we can configure bootstrap and font-awesome third party CSS files to work with SharePoint Framework solutions while leveraging React Framework.