Handling navigation in a SharePoint Framework application customizer

August 23, 2018

Probably one of the most difficult things when working with application customizers is how it behaves while navigating. Modern sites have their own page navigation mechanism which provides a great user experience as the page does not require a full-page refresh. Only the things that are changed will get loaded/unloaded from the page. This system has its perks, like for example that you cannot always control the link behaviour.

This new navigation experience is important for your application customizer and the way you implement it. There are various ways you can navigate to a page. In this article I will take the following scenarios into account:

Scenario 1

Clicking on a link to another page with the application customizer applied

Navigating back to the previous page

Scenario 2

Opening an Office UI Fabric panel

Click on a link to a page where the application customizer is applied

Scenario 3

Opening an Office UI Fabric panel

Click on a link to a page where the application customizer is not applied

Note: The panel is only used to easily show the issue which could occur, just imagine that the panel is one of your services, components, …. As there are other ways of solving this, this approach shows you how to correctly unmount your components when they are not required anymore.

The project

For covering all scenarios, I created the following application customizer:

Supporting scenario 1: watch navigation changes

The first issue that could occur happens when you use the application customizer its context in your React components. In the screenshot above you see that I have my name + URL location shown. Both values are coming from the context object provided by the application customizer.

When you would navigate to another page, check what happens:

Navigation issue 1

Nothing happens, and the reason for this is that your component doesn’t get updated. Luckily this can easily be fixed with adding a navigatedEvent listener. You can do this by adding the following code in the onInit function:

Once this is in place, every time you are going to navigate to another page, it will trigger to re-render your component.

Navigation solution 1

Now you see the URL location getting changed on every page navigation event.

Supporting scenario 2: did something change?

In the next scenario we open an Office UI Fabric panel and navigate to another page. Let’s see what happens:

Navigation issue 2

As you can see, the panel stays open. This is great because you are not doing a full-page refresh. So, the page routing system of SharePoint is working very well but could also give you some troubles.

How can you solve this? The React way would be to implement the componentDidUpdate method in the header component and tell it to undo things when an actual update happened. For instance, in this case hiding the Office UI Fabric panel.

The easiest way for this would be to check if the list ID or list item ID got changed, but there is an issue now. If you would integrate it like this:

The above code would not work, as both the prevProps.context and this.props.context reference the same object. The list id and item id check will always be false.

Issue with the list and item change validation

A better way is to pass the list and list item IDs from within the application customizer itself.

The code in the componentDidUpdate method would look like this:

Once that is in place, it should have solved this issue:

Navigation solution 2

Scenario 3: correctly disposing of your components

Another issue that could happen is when you navigate to a page where the application customizer is not enabled. To show you the issue I will open the same Office UI Fabric panel and navigating to the page without the application customizer.

Navigation issue 3

Did we not just solve the closing panel issue? Yes, we did, but this only works on pages/sites where the application customizer is active. On page/sites where it is not active, the code is not actually running. Which is true, but if you navigate from a page where you had the controls enabled, they will still be known to your browser.

Components are not unmounted

The issue here is that they are not unmounted/disposed of the page. But there is another issue. Notice that when I navigate back to the page where the application customizer is enabled. The application customizer is not displayed anymore. This will not be the case for all projects, but what I did in this sample solution. I created a singleton which gets initialized with the context of the application customizer. I also implemented the componentWillUnmount method to dispose of the singleton once the component gets unmounted from the page, but this never gets called because the header never gets unmounted. When navigating back to the page, it will use the old context which causes issues:

Property is undefined

To solve both of these issues, you will have to implement the onDispose method in the application customizer itself. The onDispose method gets called whenever the SharePoint tells the loaded placeholder to dispose of their content. This is what you define in the placeholderProvider.tryCreateContent method.

If your placeholder contains only HTML, you could just remove all nodes, but in React it is a bit different. In order to correctly dispose the loaded, React components, you can make use of the ReactDom.unmountComponentAtNode method. This method requires the DOM element in which the components are loaded.

Once this is implemented, this should solve both my Office UI Fabric panel issue and my singleton disposal.

Navigation solution 3

Notice that when navigating to the page where the application customizer is not enabled, the console logs that it is unmounting the header component. This means that the disposal code does its job. Also, the Office UI Fabric panel is gone. As all the components got correctly disposed of, navigating back to the page is not causing any issues anymore for the singleton, this gets initialized again and the application customizer gets displayed correctly.