Recently while working on the virtual PCF control we noticed that the updateView() in the virtual PCF works in a different manner as compared to standard PCF control with async functions. In standard PCF control, the return type of the updateView() is void while in the virtual PCF the return type is react-element. So when we return the promise to the updateView() by calling the callback function in a virtual PCF control, we do not get the desired result.
Let’s take an example where we will create a virtual drop-down PCF control. We will be showing the entity attributes as options in our drop-down control. In the example below, we have created an async function GetEntityAttribute which will be responsible for getting the entity attributes to list out in our PCF drop-down control.
If we call our async function GetEntityAttribute in the updateView() as follows, we will not get the options with dropdown as shown in the below image.
UpdateView() Then:
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
//Local variables let isDisable: boolean = false; let isMultiple: boolean = false; let dropDownProps: any; //Calling function to get the attributes this.GetEntityAttribute(context); //Calling function to get the selected values this.selectedItem = this.getSelecetdItems(context) dropDownProps = { entityMetadata: this.entityAttributes, defaultSelectedOptions: this.selectedItem, isMultiple: isMultiple, isDisable: isDisable, notifyChange: this.notifyChange.bind(this), context: context }; return React.createElement( DynamicDropdown, dropDownProps ); }
Field Interface then:
It happens because when an async function is called and executed the program control directly jumps to the next line of the updateView() and executes it first and renders a dropdown control with no options. It then goes again to the async function to execute the code after await where we had our array of all attributes. Since the control was rendered already, we will not get a drop-down with options.
To solve this issue we will use the useEffect() function of the react and call async functions inside it rather than calling the async function in the updateView(). Thus, the updateView() will now look as follows.
UpdateView() Now:
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement { //Local variables let isDisable: boolean = false; let isMultiple: boolean = false; let dropDownProps: IDynamicDropdownProps = {}; let entityName: string | null = ""; entityName = context.parameters.entityName.raw ? context.parameters.entityName.raw : context.parameters.fieldName.raw; //Reading the parameters dropDownProps = { isDisable: isDisable, isMultiple: isMultiple, entityName: entityName, context: context, notifyChange: this.notifyOnSelect.bind(this) } return React.createElement( DynamicDropdown, dropDownProps ); }
We will create a Helper.ts file containing the functions and then create the object of the class containing all the functions in App.tsx file which consists of a functional component. We have also passed a dependency to ensure that the useEffect() gets called every time there’s a change in the entity name.
Helper.ts
public GetEntityAttribute = async (entityName: any) => { //Local variables let functionName: string = "GetEntityAttribute()=>"; let res: any; let attributesLogicalname: any = []; let result: any; let entityAttributes: any = []; try { if (!this.isValid(entityName)) { return { options: entityAttributes } } //Making request for entity metadata to get logical names of the attributes res = await this._context.utils.getEntityMetadata(entityName); //Getting all logical name of the attributes and storing it in the array attributesLogicalname = res._entityDescriptor.AttributeNames; //Making request to get logical name as well as display name of the attributes result = await this._context.utils.getEntityMetadata(entityName, attributesLogicalname); //Converting the object keys to an array for iteration let response = Object.keys(result.Attributes._collection); //Pushing the attributes into the array for (let j = 0; j < response.length; j++) { if (result.Attributes._collection[response[j]].DisplayName != null) { entityAttributes.push({ key: result.Attributes._collection[response[j]].LogicalName, text: result.Attributes._collection[response[j]].DisplayName }) } } entityAttributes.sort(function (a: any, b: any) { if (a.text < b.text) { return -1 } if (a.text > b.text) { return 1 } return 0; }); } catch (error: any) { //Handling error this._context.navigation.openErrorDialog({ message: functionName + ": Kindly check the entity name and it must be in lowercase" }) } return { options: entityAttributes } } App.tsx: useEffect(() => { _CrmHelper.GetEntityAttribute(props.entityName).then((success) => { //Setting the dropdown options setDropDownOptions(success.options) }); }, [props.entityName])
Field Interface Now:
Conclusion
Thus, we saw how to use Async-await on a load of virtual PCF control.