Async requests inside PCF init and updateView

In quite a few cases, when we develop PCFs we need to make external requests. Even if the runtime provides metadata for the properties we define, sometimes we need some more. Or maybe we need to grab some more data from Dataverse or an external service.

One easy way would be to make this requests inside the “init”, since it’s done only once. Or it could be done inside updateView, just before render. But is it a good way? I was asked a few time about this subject, and I think some issues in the community forum are based on this problem. So I’ve dome some tests.

The “init” method

Let’s start with the sdk definition:

Used to initialize the component instance. Components can kick off remote server calls and other initialization actions. Dataset values cannot be initialized here, use the updateView method to achieve that.

Even the definition mentions remote server calls. But will the framework runtime wait for that promises? The signature of “init” doesn’t specify anything about return value

So one could think that we can return a promise from the init function, and the platform would wait for the init to complete. But it doesn’t. The updateView will be called soon after the init was executed, even if the Promise was not resolved or rejected.

public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
{
   //if you do this, remember that updateView could be called before the promise was resolved
   //the "return"  won't help to wait for the Promise
	return this.retrieveSomeData().then((data) => {
		this.renderGrid(context, data)
	})
}

So if we make this kind of requests inside init, remember to save the data in a property, which you can grab on each render started from updateView. That’s for the case wheer it’s not a “dummy” PCF, which doesn’t need to reflect the platform interaction made through calling updateView.

In the past 2-3 years, I’ve got used to think to “init” being called without data. The values of the properties were “null” inside init method. The values were provided only in “updateView”. The tests I’ve done today had the values even in the init methods. We’ll see if this is something will stay. For now I wouldn’t rely on that.

The “updateView” method

Let’s see the docs for updateView:

This method will be called when any value in the property bag has changed. This includes field values, datasets, global values such as container height and width, offline status, component metadata values such as label, visible, etc.

In one of my first blogs I’ve wrote about how often the “updateView” is called. At that time I had a field PCF in mind. Every time we change the value of a property (so by calling notifyOutputChanges), the platform decides if the value can be accepted (and calls the updateView with the new value) or rejected (and calls the upadetView with the old value). But there are a lot of other cases where the updateView will be called.

With a dataset PCF, the updateView is called even more often: when the dataset inside the PCF is changed by using the save methods, when the dataset is refreshed, changes the page or the sorting, when records are selected/deselected, when the width or height of the grid was changed, and some more…

For updateView the signature is clear: the function returns void.

Even if we change it to Promise<void>, it won’t change anything; the framework runtime won’t wait dor the promise.

So if we have a async request inside updateView, you need to think that updateView could be called again, even if the previous call to updateView didn’t ended.

Some measurement tests

Dataset PCF

I’ve used a field PCF and a dataset PCF. In both I’ve included a logging function inside my render function. I’ve also added a private property “index” used to count which call to updateView I’m processing.

private renderGrid(context : ComponentFramework.Context<IInputs>, delay : number, index1: number, message: string){		

//the delay for setTimeout is changable
//added a few properties to compare: selectedRecords, updateProprties, the length of SortedRecords
//made a closure, so I can compare the value on start, with the value after the timer

const fn = ((ind: number, delay1:number, dataset, context, selectedRecords, updatedProperties, sortedRecordsLength) => () => new Promise(function(resolve, reject){
	console.log(`${message} start: ---- ${ind} ----`, dataset.getSelectedRecordIds());					
	window.setTimeout(() => {
		console.group(ind);				
		console.log(`render grid : ${ind}`, dataset.getSelectedRecordIds());				
		console.log(selectedRecords);				
		console.log("updated properties from context:", context.updatedProperties);
		console.log("updated properties on closure start:",updatedProperties);
		console.log("sortedRecordsLength on closure start", sortedRecordsLength);
		console.log("sortedRecordsLength", dataset.sortedRecordIds.length);
		console.log(`----- ${message} ${ind} done`);
		console.groupEnd();
		resolve("done");
	}, delay1);
}))(index1, delay, context.parameters.dataset, context, context.parameters.dataset.getSelectedRecordIds().join(" , "), 
context.updatedProperties.join(" , "), 
context.parameters.dataset.sortedRecordIds.length);
fn();
	
//.....
}

Inside the “init” I call the render function with a delay of 800ms

public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
{
	this._container = container;			
	context.mode.trackContainerResize(true);
	return this.renderGrid(context, 800, this.index, "init ----");
}

Inside the updateView I have a random delay:

public updateView(context: ComponentFramework.Context<IInputs>): Promise<any>
{	
	this.index = this.index + 1;
	return this.renderGrid(context, 
        Math.random()*1000 * this.index, this.index, "updateView ---");
}

In the following screenshot we see 3 render calls, one from init and two from updateView. In this example happens that the render from init is done after the render from updateView

Could that cause problems? Let’s see what happens with some context properties. In the following screenshot we see the logs after selecting 5 records in the grid (each selectRecordId triggers an updateView):

Given the random delay (simulating a request), notice the order: 1, 2, 4, 5, 3.In the log above, the red marked ids are the ones written using

console.log(selectedRecords);

The green markes logs were written using

console.log(`render grid : ${ind}`, dataset.getSelectedRecordIds());	

They are different, because the “selectedRecords” are the state before the timer started, while the dataset.getSelectedRecordsIds() represents the current situation. That means that the dataset/context reference didn’t change, and the call returns always the current state (even the dataset had some other records selected on start)

Here is another example; here I’ve selected some records and made some updates to the dataset (using the dataset save methods explained in my other blog )

Notice the order of updateView execution after the delay (22, 25,32, 27, 26, 33, 35, 24,23, 21, 34, 30, 29) and also the content of context.updatedProperties compared with the state of updateProperties when the render function started.

Even if we work with async requests inside init and updateView, we might not even notice problems as long we pass mutable data (like the context). But we might get problems if we pass mutated data or primitive data types extracted from the context.

Field PCF

For the measure on a field PCF I’ve used the TwoOptionsComposite. It’s a control allowing to change the value of up to 30 booleans (two options).

I’ve used a similar logging as the dataset example above, with async requests inside updateView. I’ve cliked on the first 5 fields in the order 1 to 5; each one changed a value, so triggered an updateView. And here are the logs

Notice that using the random delay we’ve got the render executed in the order 1, 4, 5 , 2, 3 (red numbers).

Actually the last render should have beed with all fields on “true” (red marked, click no. 5). Instead, the last render is made with data after click no. 3, where the cards 4 and 5 was still false (marked green).

For logging I’ve used this function (for each card I have some mutated data , followed by the current value read from context.

const fn = ((ind: number, delay1:number, cardArray, updatedProperties) => () => new Promise(function(resolve, reject){		
	window.setTimeout(() => {
		console.group(ind);				
		console.log(`render : ${ind}`);		
		console.log("context.updateProperties", context.updatedProperties);	
		console.log("updateProperties when the updateView started: --", updatedProperties);	
		console.log(`card1: ${cardArray[0]} ${context.parameters.boolean1.raw}`);	
		console.log(`card2: ${cardArray[1]} ${context.parameters.boolean2.raw}`);
		console.log(`card3: ${cardArray[2]} ${context.parameters.boolean3.raw}`);
		console.log(`card4: ${cardArray[3]} ${context.parameters.boolean4.raw}`);
		console.log(`card5: ${cardArray[4]} ${context.parameters.boolean5.raw}`);
		console.log(`----- ${message} ${ind} done`);
		console.groupEnd();
		resolve("done");
	}, delay1);
}))(index1, delay, [...cards.map((card) => card.control.raw)], context.updatedProperties.join(","));

fn();

How I work with requests?

As we saw above, often we don’t even notice that the render was started with older events. So maybe it’s enough to be aware of the pitfalls. It depends what kind of parameter we provide. But if it makes problems, it’s that kind of problem hard to track, since we have this only sometimes.

Personally I prefer to use React. Both in init and updateView I start the render, and treat the requests inside my react component. Since I prefer function components, I make the requests in custom react hooks, and let React take care of everything. That way the requests are separated from my components, and it uses always the latest values.

Something like this

export class MyClass implements ComponentFramework.StandardControl<IInputs, IOutputs> {
...
public init(context: ComponentFramework.Context<IInputs>,...)
	{
		return this.renderGrid(context);
	}


public updateView(context: ComponentFramework.Context<IInputs>): void
	{	
		return this.renderGrid(context);
	}

private renderGrid(context : ComponentFramework.Context<IInputs>){
  //... prepare the props...
  const props = {...}
  ReactDOM.render(React.createElement(ColorfulGrid, props, this.container);
}
...
}

And the component looks like this

export const ColorfulGrid = React.memo(function ColorfulGridApp({myProps}): JSX.Element {

  const {myRequestedData} = useCustomHook(...);

  return <.../>
},(prevProps, newProps) => {
    return prevProps.myProps === newProps.myProps 
});

I make the async requests inside my custom hook (useCustomHook in the example).
I’ve packed everything inside the React.memo, in order to optimize the rendering: even if updateView will be called a lot, and that triggers my render function, React won’t render until my needed props changes.

export const useConfig= (dataset, ...) => {
  const [metadata, setMetadata] = React.useState();
  
  React.useEffect( () => {
    //retrieve the dataset here
    //then call setMetadata(...)
  }, [dataset]);

   return {metadata}
}

By using React.useEffect with a dependency on dataset, the request will be executed only by mounting and if the dataset changes.

Hope this helps!

Photo by Andrea Piacquadio from Pexels

6 thoughts on “Async requests inside PCF init and updateView

Add yours

  1. Hi Diana,
    I prefer to use ‘context.factory.requestRender();’ when the asynchronous operation has finished loading external data.

    1. Hi Betim,
      Thanks for sharing from your experience.Good to know about other ways that work.
      Personally I avoid context,factory.requestRender, because the context.factory is documented as “Provides properties and methods to work with Popup services.”, so I’m afraid it was not designed for this purpose.
      https://docs.microsoft.com/en-us/powerapps/developer/component-framework/reference/factory?WT.mc_id=BA-MVP-5004107
      I’ve read some post about forcing the rerender, but it never happened to have the need to force it that way until now. I’ve relied on the framwork runtime.
      But it’s always good to know the options! 😀 I’ll keep it in mind!

  2. Hi Diana,
    Again happy to see another in-depth PCF article!
    For the conclusion that if we left everything to React to decide, in this case we have lost the ability to control PCF with the lifecycle provided by PCF framework. A very simple example is Xrm.Page.data.refresh(), if we put everything in react, then probably the async request will not be triggered since there is no change from Context provided from PCF.

    That requirement happened in some of our complex PCFs, while loading PCF we need to fetch other entities which cannot direct link to the PCF’s dataset, and the other entities will change while the PCF’s dataset remain unchanged.

    So I think the solution should depends on the need of async request, if the request means to trigger every time when PCF get refreshed, then it has to be inside updateView, if the request only need to trigger once when initializing, it will be good to put them inside component and let React to decide.

    1. HI Jerry,
      Thanks for sharing your opinions:-) You’ve brought a few ideas in the comment, so I’ll go to each.

      – Refresh() triggers updateView or not: I have an older blog about when the updateView is called: https://dianabirkelbach.wordpress.com/2020/03/29/pcf-when-is-updateview-called/ You can see there, that a refresh(), save(), autosave or window resize (and even a lot of other cases) will trigger an updateView.

      – You don’t lose the control if you let the react handle the changes, because in each updateView you would call the React render function. React doesn’t really recreate everything, it just decides if something in the needed props changed. The props to the react component are not the whole context. The Component doesn’t need to know that it is placed in a PCF, so the props are the paramerers needed are the values it depends on. At least I do it that way.
      But you still can make async requests inside the React component. For instance I use React function component and React Hooks. The useEffect hook has the option to render “only once” or on change of specific variables. So you have the control, without having to place everything inside the updateView itself.
      It’s even better, because while React loads the data, you can show a loading spinner, or something similar, so the user has feedback.

      – About the need to load tables that cannot be linked to the dataset: Sometimes that can be the case, but in a lot of cases you can define a second/third dataset. That way the maker can map the views he/she needs, and you can take care in the code and filter the data based on the parent record you are in.

      At least I didn’t had problems leting React handle everything. Let’s see if I’ll get to such a case…

      1. Yeah, why we didn’t get that idea to use multiple dataset! Thanks Diana, we definitely need to try that…

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: