Ever dreamed of injecting a beautiful React component across all the pages on a SharePoint site? With the observer pattern, this is easily achievable.
Table of Contents
- Table of Contents
- Introduction
- Using the Observer Pattern to Inject React Elements
- Handling SharePoint’s ‘Shy’ Mode
- Putting It All Together
- Optimization and Performance Considerations
- Detailed Implementation & Code
- Conclusion
Introduction
Integrating custom React components into SharePoint’s standard UI can be challenging. It may work in some areas, but not all, for example, when you have to deal with dynamic page elements like the ‘shy’ header that appears upon scrolling. In this blog post, I will show how to use the observer pattern to effectively inject a React element into the SharePoint header. I will also show how to handle SharePoint’s ‘shy’ mode to ensure your injected elements remain visible.
Using the Observer Pattern to Inject React Elements
The observer pattern is a software design pattern in which your observer can monitor any DOM state changes and get notified. In the context of SharePoint and React, we can use the MutationObserver
API, which is part of the DOM (Document Object Model) Standard defined by the WHATWG (Web Hypertext Application Technology Working Group)
. This API allows us to watch for changes in the DOM and inject our React component when the target element becomes available.
By using the MutationObserver
API—widely supported in modern browsers—we can efficiently monitor the DOM without resorting to less efficient methods like polling.
Injecting the MenuWidget Component
Our goal is to inject a MenuWidget
React component into the SharePoint header. We’ll observe the DOM for the presence of specific elements and render our component once they’re available.
Simplified Code Snippet:
private observeForElement = (): Promise<Element> => {
return new Promise((resolve) => {
let observer: MutationObserver | null = null;
observer = new MutationObserver(() => {
const divHook = document.querySelector('*[data-automationid="SiteHeaderFollowButton"]');
// Other selectors...
const parent = divHook?.parentElement?.parentElement;
if (parent) {
if (observer) observer.disconnect(); // Stop observing once found
resolve(parent); // Resolve with the found parent
}
});
const targetNode = document.querySelector('*[data-automationid="SiteHeader"]');
if (targetNode) {
observer.observe(targetNode, { childList: true, subtree: true });
} else {
throw new Error("Target node 'SiteHeader' not found");
}
});
};
In the code above:
- We create a new
Promise
that resolves when the target elementSiteHeaderFollowButton
is found. - A
MutationObserver
watches for changes within theSiteHeader
div. - Once the target element is found, we disconnect the observer and resolve the promise with the parent element.
- We then render our
MenuWidget
component into this parent element.
Visualizing the Injection Process
Production code may be more robust by using multiple observers to ensure it works in various situations, such as displaying document libraries, lists, and more.
Handling SharePoint’s ‘Shy’ Mode
SharePoint’s ‘shy’ mode refers to the behavior where the header changes when the user scrolls down, you may have noticed that the header changes, causing injected elements to disappear if they’re not handled correctly. To ensure our MenuWidget
remains visible, we need to observe changes to the header and re-inject our component when necessary.
Activating the Shy Observer
We can set up another MutationObserver
to monitor changes related to the ‘shy’ header and render our component accordingly.
Simplified Code Snippet:
private activateShyObserver = () => {
const observer = new MutationObserver((mutations_list) => {
mutations_list.forEach((mutation) => {
mutation.addedNodes.forEach(() => {
const parent = document.querySelector('div[class^=shyHeader]');
if (parent) {
let shyApps = document.getElementById('UserAppsShy');
if (!shyApps) {
shyApps = document.createElement('div');
shyApps.id = 'UserAppsShy';
parent.appendChild(shyApps);
const appContext = new AppContext(
this.context,
this.logger
);
const element: React.ReactElement = React.createElement(
AppContextProvider,
{ appContext },
React.createElement(MenuWidget)
);
ReactDom.render(element, shyApps);
}
observer.disconnect();
}
});
});
});
const headerRow = document.querySelector(`div[class^=headerRow]`);
if (headerRow) {
observer.observe(headerRow, { subtree: false, childList: true });
}
};
In this code:
- We observe the
headerRow
element for any child list changes. - When the ‘shy’ header appears, we inject our
MenuWidget
into it. - We ensure that the component is only injected once by checking if it already exists.
Visualizing the Shy Mode Handling
Putting It All Together
When we combine these two observers, we ensure that our MenuWidget
component is always present in the SharePoint header, regardless of user interactions like scrolling.
Optimization and Performance Considerations
The observer pattern is effective for injecting React components into SharePoint. Here are some best practices from the field :
-
Monitoring DOM Structure Changes (Note: Not Officially Supported by SharePoint): Manipulating the DOM directly, as shown in this post, is not officially supported by the SharePoint team. This means that such customizations may break without notice if Microsoft makes updates to SharePoint Online. Always proceed with caution and ensure you regularly test your solutions after SharePoint updates.
-
Specificity of Observed Elements: To optimize performance, limit the scope of the HTML elements you’re observing. By targeting the most specific parent element possible, you reduce the number of DOM mutations the observer needs to process. This minimizes overhead and prevents unnecessary performance degradation.
-
Understanding Observer Parameters: Experiment & test with the
subtree
andchildList
options when creating your observer. These parameters determine which mutations are observed:childList
: Set totrue
to monitor the addition or removal of direct child nodes.subtree
: Set totrue
to extend monitoring to all descendants of the target node.
By configuring these options appropriately, you can fine-tune the observer to watch only for relevant changes, and ensure optimal performance.
Detailed Implementation & Code
You can find the full implementation of this pattern in the User Apps Application Customizer .
Conclusion
Injecting React components into SharePoint can be made reliable and efficient by leveraging the observer pattern. By observing the DOM for specific changes, we ensure our components are injected at the right time and remain visible, even when SharePoint’s dynamic behaviors, like the ‘shy’ header, come into play.