Skip to content

Instantly share code, notes, and snippets.

@cwparsons
Last active April 20, 2021 21:06
Show Gist options
  • Save cwparsons/bcc7f0b0d266356aadfa63f637c7aa80 to your computer and use it in GitHub Desktop.
Save cwparsons/bcc7f0b0d266356aadfa63f637c7aa80 to your computer and use it in GitHub Desktop.
/**
* Create a SharePoint theme context provider, allowing children components to
* use the SharePoint theme and update when the theme changes.
*
* Usage:
* ```typescript
* <SpThemeProvider observer={this} serviceScope={this.context.serviceScope}>
* {children}
* </SpThemeProvider>
* ```
*
* ```typescript
* const theme = useTheme();
* ```
*/
import { IReadonlyTheme, ThemeProvider, ThemeChangedEventArgs } from '@microsoft/sp-component-base';
import { ISPEventObserver, ServiceScope } from '@microsoft/sp-core-library';
import { isEqual } from '@microsoft/sp-lodash-subset';
import { createContext, createElement, useContext, useEffect, useRef, useState } from 'react';
/**
* Create the React context for storing the SharePoint theme.
*/
export const SpThemeContext = createContext<IReadonlyTheme>(undefined);
type SpThemeProviderProps = {
children?: React.ReactNode;
observer?: ISPEventObserver;
serviceScope: ServiceScope;
};
/**
* Create the provider element that gets the SharePoint theme from the
* ThemeProvider, and handles changes to the theme using React state.
*/
export const SpThemeProvider = ({ children, observer, serviceScope }: SpThemeProviderProps) => {
// Use a ref to lazy load the ThemeProvider service.
const themeProviderRef = useRef<ThemeProvider>(null);
// Only load the ThemeProvider service once.
if (themeProviderRef.current === null) {
themeProviderRef.current = serviceScope.consume(ThemeProvider.serviceKey);
}
// Use a function to only get the theme once.
const [theme, setTheme] = useState<IReadonlyTheme>(() => themeProviderRef.current.tryGetTheme());
// Add events to handle changes to the theme.
useEffect(() => {
if (!observer) {
return;
}
const themeChangeCallback = (themeChangeEventArgs: ThemeChangedEventArgs) => {
// Only change the state if the theme is different.
if (!isEqual(theme, themeChangeEventArgs.theme)) {
setTheme(themeChangeEventArgs.theme);
}
};
themeProviderRef.current.themeChangedEvent.add(observer, themeChangeCallback);
return () => {
themeProviderRef.current.themeChangedEvent.remove(observer, themeChangeCallback);
};
}, [theme]);
return createElement(SpThemeContext.Provider, {
children,
value: theme
});
};
SpThemeProvider.displayName = 'SpThemeProvider';
export const SpThemeConsumer = SpThemeContext.Consumer;
export const useTheme = () => {
return useContext(SpThemeContext);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment