PCF: Caching

When writing PCF components, the Framework already provides the metadata for the component and the properties defined in the manifest. For instance for an OptionSet control, we get all the available options. And that’s already a great advantage compared with a plain HTMLWebResource. But sometimes that’s not enough. Maybe you need related data that won’t change very often. Or maybe the metadata is not related to your control, (like when it’s kind of component for another metadata). Or maybe you have the configuration in a JSON WebResource or environment variable: because of a current limitation of PCF input parameters length or maybe the component is a part of a bigger suite, having a central global configuration. So you need to make requests for the data or metadata. Since the requests cost time and performance, would be good to cache it. Here is the result of my investigation about caching possibilities outside the component.

State

The possibility that the PCF itself offers, is the state property. The state is the 3rd parameter for the “init()” method. The description in the sdk doesn’t say much:

The component state that is saved from setControlState in the last session

But in the examples provided with the sdk there is a good example, describing some more about the intention of the state: https://docs.microsoft.com/en-us/powerapps/developer/component-framework/sample-controls/control-state-api

…persist state of component across multiple renderings of the component within the same session.

…maintain user state throughout the user’s session as the user navigates to and from the component.

But what does it mean exactly?

  • The state will be saved for exact one instance of the PCF component and for exact the same record
    • I’ve customized the same attribute with the same control twice, using the TSControlStateAPI from the sdk examples. Each of these 2 controls had his own state.
    • When I opened another record, the state was null. But when I’ve opened the first record again, the state saved with the first record was provided in the init method.
    • I could navigate to other entities and come back, the store was still present for the record I’ve visited already.
  • When is the state preserved:
    • when open again the same record
    • formContext.ui.refresh() triggers the PCF-“init”; there is the state provided.
    • formContext.getControl().setVisible(true) – If the control was hidden, and the setVisible(true) method is called, the init will be called, and there we get the state provided (not null).
      • Calling setVisible(false) won’t call the init method, so there doesn’t matter.
  • When is the state not preserved (even if the record was visited and the state was set):
    • The state doesn’t work like a browser storage: when I’ve opened another browser-tab, the state was null. So it seems to me like a React storage, not like a session storage
    • The state is also null after a browser-refresh (“F5“)

How to implement it?

The implementation is very easy. Everywhere in your code just set the state using context.mode.setControlState() method. Next time the init() is called, you will get the state as a argument.

private  _stateDictionary : ComponentFramework.Dictionary = {};

//....
this._stateDictionary["yourKey"] = yourValue;
//of course you can have more keys in your state to set
 this._context.mode.setControlState(this._stateDictionary);
//the you get the state with the init method 
public init(
       context: ComponentFramework.Context<IInputs>, 
       notifyOutputChanged: () => void, 
       state: ComponentFramework.Dictionary, 
       container:HTMLDivElement): void
{
if (state)
    {
         this._stateDictionary = state;
    }
}

What can be cached inside the state?

The state can store basically everything. The signature of the init method in TypeScript says that state is a ComponentFramework.Dictionary. So basically a JSON object.

The values for the keys are not limited to strings or basic data types. I tried out with functions and even a instance of a class. It worked. I could set them, and I could use the function and the class instance provided by the init() state argument without issues.

So where can the state be useful?

Given the behavior of the state argument, I would use the state to cache the position/progress/point in a list. But I think it can also be used to cache some related data. For instance if you need some information about an 1:N relationship (for instance if the control is showing the count of the related activities). But if it’s a kind of metadata, I think the state is not the best suited approach, because the same data needs to be shared between all instances of the components, even if the record was not visited already.

What about Canvas Apps?

The behavior described above is inside a Model Driven App. For a Canvas App, at least for field components, the state has another meaning. That’s because of the way a CanvasApp works: when you have a gallery and go in a form to edit the data, the canvas app will reuse the same form, and that’s why you would get the state set with the previous record that was edited.

So in CanvasApp you could use the state to save metadata or other almost static data, but it’s not suitable to save data related to a record. Writing a PCF component for both Model-Driven and Canvas App would make very hard to make use of the PCF state.


Browser storage

But what if you write a PCF for a Model-Driven App and need to use the data/metadata/configuration for all component instances, for all records? For example, you might have a configuration saved in Environment Variables. In that case you can use the browser storage to cache: localStorage or sessionStorage. In case you need to save a lot of data you could even consider IndexedDB, but that’s a little complicated, and probably not necessary.

LocalStorage or SessionStorage

You can choose which browser storage you prefer. The localStorage would save the data even after the user closes the browser, but the problem with localStorage is that you need to implement a mechanism to refresh the content when the metadata/configuration has changed. That’s of course possible, but requires some more code. So I think the easiest way is to chose the sessionStorage.

An important thing to keep in mind is that when using sessionStorage you share the same storage with all the model-driven/canvas-apps running in your domain in the same session. The same session means also, when you open another tab in your browser. And Microsoft is saving also some keys in the localStorage/sessionStorage and another 3rd parties (pcfs) could do that too. So you need to choose a unique key. Also you should keep in mind the with the same browser you could visit another organizations (dev, test, prod), and there you might need the same key but with other values. So would be good if the key includes at least your customization “prefix” and “organization unique name“; maybe even more (maybe sometimes makes sense to include the user language-code in the key too). The key becomes even more important it you choose the localStoarge.

You can only save strings in the sessionStorage. You can use JSON.stringify before setting a key in the storage, and JSON.parse to read the object back from the storage, but it has to be a plain object (you cannot have classes with methods/functions attached).

It’s easy to use:

sessionStorage.setItem('key', 'value')
sessionStoarage.getItem('key')

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 )

Google photo

You are commenting using your Google 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

Blog at WordPress.com.

Up ↑