UpdateView in Virtual PCF Components and how to optimize rendering

Remember one of my first blogs, in which I was talking about how often the updateView method of a PCF is called (it’s here). When you develop PCFs, you get used with updateView being called often. Even when there is only one change in the parameters, you might get the updateView being called a few times. So it’s important to optimize your component and to render only when needed.

The optimization is not really different for Virtual Components, but with Standard Components you have other ways of optimizing too. With Virtual Components you need to use the React ways.

If you are aware of updateView lifecycle, you might want to scroll down to Virtual Components chapter.

The Role of “updateView” in Standard Components

In that older blog I’ve tried to show how often the updateView is called. And that was only based on a field component. If we have a dataset component there are a few more calls.

To improve the performance, we need to prevent rendering when is not needed. But updateView is very important for the PCF lifecycle. We rely on the Framework Runtime to update our component every time there is a change.

Illustration of the life cycle process (from learning modules for PCF)

If we think to React components, I would extend this process: the component relies to be called by updateView on each change. We don’t need to keep the value inside the React component state: we always get the right value from the updateView and pass it through the props.

React Standard Components

So when we want to optimize the components, we should not block all the re-renders triggered from updateView, but we should block the unnecessary ones.

a. Conditional approach

One way to approach this, is by checking if something changed and call the render only if necessary. Something like this

	public updateView(context: ComponentFramework.Context<IInputs>): void
	{	
		if(context.parameters.dataset.loading===false && context.updatedProperties.includes("....")){
                  this.renderMe(context)
		}
	}

If you need to make requests, you could even consider making the requests first and only render after that. I’ve wrote another blog why this is not recommended, but there might be cases where you cache the response so you could have done something like this:

	public updateView(context: ComponentFramework.Context<IInputs>): void
	{	
		makeACachedRequest().then((response) => {
			this.renderMe(context)
       });
	}

b. Always render

But the most straight forward way to work with React is by calling your render function each time, and let React decide when it should update the DOM or not:

public updateView(context: ComponentFramework.Context<IInputs>): void
	{ 
      const props = {...}	
      ReactDOM.render(React.createElement(ColorfulGrid, props ), this._container);
	}

If you worry that React will recreate everything over and over again, you don’t have to. Have a look to this React documentation: React only updates what’s necessary.

There are still some cases when your component is rendered too often. I’ll talk about that below, in the “Optimizing the React Component” chapter.

Virtual Components

The Virtual Components are a new type of code component (here is the announcement). Basically, the virtual components are developed similar to standard components, with two big differences:

  • The components doesn’t need to include their own React and FluentUI library ( as Standard Component does). The virtual ones are allowed to use the Power Apps own React and FluentUI. This means that the components are much smaller (and loaded faster). That doesn’t only mean that each PCF will get faster, but also the overall App performance increases.
  • The components are attached to the platform React tree. The Virtual Components are treated now like the 1st party components.

Have a look at this short demo. Observe the back borders around the tab containing my PCF during the rendering of the controls. In the first part of the video, it’s a standard control. In the second it’s the same PCF as virtual; but there the rendering is instant, so you can hardly see the delay.

UpdateView inside Virtual Components

The structure of a Virtual Component is slightly different than a Standard Component: it implements a “ReactControl” instead of “StandardControl”

export class ColorfulOptionset implements ComponentFramework.ReactControl<IInputs, IOutputs> 

and the updateView returns a ReactElement

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement
	{
		const params = { //...
		};			
		return React.createElement(ColorfulOptionsetControl, params );
	}

So updateView needs to return a React element. No ifs, no (asynchronous) requests before rendering; just create the element and return it.

That means, that Virtual Components cannot use the conditional approach (a.) from Standard Components. It needs to always create the React element (b.).

Optimizing the React Component

Let’s see how we can optimize the component if we always render. For that we need to use the possibilities offered by React. There are two types of React components, function components and class components, and each of them works different.

Function Components

A react function component is a function which accepts props as an argument and returns a React element. The state can be handled by using React Hooks (like useState).

This function will be called each time updateView is called. In the following example (ColorfulOptionset PCF) I’ve logged when the updateView and when a render is called

export const ColorfulOptionsetControl = ({...): JSX.Element =>{      
  console.log("%cEntered control", "color:red");
}

When the value is changed there is no unwanted rendering, because the value is not in the internal state (so I don’t trigger a rerender by myself, and rely on updateView calling my component). But the form save triggers the updateView three times, and each time it triggers a re-render.

To avoid re-rendering when it’s not necessary, we can use React.memo. This makes a shallow compare of the props. If the props are equal, it will skip rendering, and reuse the last rendered result.

So instead of:

const ColorfulOptionsetControl = (props:IColorfulOptionsetProps): JSX.Element =>{  
 return <>...</>
}

we will wrap everything inside React.memo:

const ColorfulOptionsetControl = 
   React.memo((props:IColorfulOptionsetProps): JSX.Element =>{  
       return <>...</>
   })

By adding the React.memo around my ColorfulOptionset, during the form-save, no unnecessary render gets triggered

In case that’s not enough, there is a second parameter for React.memo: a function where we can make the comparison by our own. Will return true if the old and new props are equal. Usually we won’t need that, but it could be also useful for troubleshooting:

export const ColorfulOptionsetControl = React.memo((props:IColorfulOptionsetProps): JSX.Element =>{  
 return <>...</>
},  (prev, next)=> {  
  return prev.someProp ===next.someProp //make your own equal here or just log the props
}
)

Class Components

React Class component is a class extended from React.Component. It get props, has an internal state and a render function returning JSX.

In order to prevent unnecessary renders, the easiest way is to use React.PureComponent instead of React.Component. The PureComponents automatically implement a shallow prop and state comparison. If they are equal, it’ll skip re-rendering.

I’ve took the new ReactFacepile PCF from the SDK (the virtual one), which implements a React.Component

export class FacepileBasicExample extends React.Component<IFacepileBasicExampleProps, IFacepileBasicExampleState> {
...
   render(){
      return <>..</>
   }
}

and measured how often it re-renders when I change a value with a click (so I don’t trigger a lot of events, like dragging would have) and also when I save the form. When I change the value, I want my component to rerender , because the value was changed (but only once). In the case of saving, the value doesn’t change so I don’t want a re-render.

For that I’ve added console log in updateView

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
		console.log("entered UpdateView", context.updatedProperties);
... 
}

and inside the component render function

public render(): JSX.Element {
	console.log("%cEntered component render", 'color:red');
    ...
}

and here is the result:

So each value-change triggered 2 re-render, and the save event triggered 3 re-render.

Only by changing the Component in a PureComponent

I get the desired behaviour:

React.PureComponent implement for us a special method “shouldComponentUpdate(nextProps, nextState)”. If the comparison made by React is not enough, we can define our own comparison by implementing the “shouldComponentUpdate” by hand. As in the React.memo example, we’ll probably never need this. But just in case… It works like this:

export class FacepileBasicExample extends React.Component<IFacepileBasicExampleProps, IFacepileBasicExampleState> {
...
public shouldComponentUpdate(
        nextProps:  Readonly<IFacepileBasicExampleProps>, 
        nextState:  Readonly<IFacepileBasicExampleState>, 
         nextContext: any) : boolean{
               //  compare this.props with nextProps and this.state with nextState
        		return true; //true means a rerender
	}
public render(){
  return <>...</>
}
}

Pitfalls

Both React.memo and PureComponent makes a shallow comparison of the props. But there are some cases where React thinks the props are different. Let’s see some examples

Recreating callbacks

Inside indes.ts my updateView looks like this:

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement
{
		let params = {
            rawOptions: context.parameters.optionsInput.attributes!.Options,
			selectedKey: this.currentValue,    
			onChange: (newValue: number |null) => {
				this.currentValue = newValue;
				this.notifyOutputChanged();
			}, 
		};			
		return React.createElement(ColorfulOptionsetControl, params );	
}

One of my properties is a callback: onChange. Because the onChange is defined inline, it will be recreated every time updateView is called. That way React will think that the properties changed, and will rerender the component.

One way to avoid that: define the onChange as a method in your class. Notice that defining it using the arrow function, “this” won’t lose the context.

private onChange = (newValue: number |null) => {
		this.currentValue = newValue;
		this.notifyOutputChanged();
	};

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement
{
		let params = {
            rawOptions: context.parameters.optionsInput.attributes!.Options,
			selectedKey: this.currentValue,    
			onChange: this.onChange, 
		};			
		return React.createElement(ColorfulOptionsetControl, params );	
}

Modifying the PCF parameters

In my ColorfulOptionset PCF I add the “Empty” option to the list. Firts I’ve implemented that in updateView: add this option and map the options to something the FluentUI Dropdown understands:

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement
{
       this.allOptions = [{Label: "--Select--", Value: -1, Color: "transparent"}, ...opts];
		this.dropdownOptions = this.allOptions.map(
                      (option : ComponentFramework.PropertyHelper.OptionMetadata ) =>  (
                    {
                    key: option.Value, 
                    text : option.Label, 
                    data: {color: option.Color}
                     }) 
        );

		let params = {
            rawOptions: this.dropdownOptions,		
		};			
		return React.createElement(ColorfulOptionsetControl, params );	
}

Of course, I’ve realized later, that this way my component will unnecessarily get rendered, because my property changes each time.

It’s not an option to keep the options as a property inside my class (maybe think that it’s enough to parse the options only in “init”), because the options might change also from outside. When somebody uses form-scripting and changes the options for the control, my React component should detect that and rerender.

The easiest way was to move the options-parsing inside my component. I pass only the “raw” options.

public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement
{
		let params = {
            rawOptions: context.parameters.optionsInput.attributes!.Options,
			selectedKey: this.currentValue,    
			onChange: this.onChange, 
		};			
		return React.createElement(ColorfulOptionsetControl, params );	
}

Another way would have been to make my own “equal” comparison for React.memo, and decide when the values inside my options array are not changed. Of course that comparison would have been more time consuming, so not the best option.

Of course, there might be a lot of examples like this. It is worth to check how often your component get rendered.

Recap optimizing

Function componentClass component
Common patterns
React.memo((props)=>{
return <>…
})
 class MyComponent extends React.PureComponent
Extended patterns
React.memo((props)=>{

return </>
},
(prev, next)=>{
return prev props===next props
})
class MyComponent extends React.Component {

public
shouldComponentUpdate(nextProps, nextState:, nextContext) : boolean{
// compare this.props with nextProps and this.state with nextState
return true/false;
}
public render(){

return </>
}
}

More about Virtual Components

I encourage you to read these excelent references about Virtual Component:

Photo by Bradley Hook

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: