Let's load a third-partly script library with react-query
!
I use a library to manage subscriptions for my product, and the only way to access their SDK is to load a script.
Here we have a usePaddleSdk
hook that returns the library. It leverages react-query
to handle asynchronous code. The query function loads the script and returns Paddle SDK.
import { reportError } from "errors/errorMonitoring"
import { useQuery } from "react-query"
import { assertEnvVar } from "shared/assertEnvVar"
import { createScript, getScriptBySrc, loadScript } from "shared/helpers/dom"
import { PaddleSdk } from "../PaddleSdk"
export const paddleQueryKey = "paddle"
const paddleScriptSource = "https://cdn.paddle.com/paddle/paddle.js"
const paddleVendorId = Number(assertEnvVar("REACT_APP_PADDLE_VENDOR_ID"))
export const usePaddleSdk = () => {
return useQuery(
paddleQueryKey,
async () => {
if (!window.Paddle) {
const paddleScript = getScriptBySrc(paddleScriptSource)
try {
if (paddleScript) {
await loadScript(paddleScript)
} else {
const script = createScript(paddleScriptSource)
await loadScript(script)
}
const paddleSdk = window.Paddle as unknown as PaddleSdk
paddleSdk.Setup({ vendor: paddleVendorId })
} catch (err) {
reportError(err, { context: "Fail to load Paddle" })
}
}
return window.Paddle as PaddleSdk
},
{
keepPreviousData: true,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: Infinity,
}
)
}
If the library is not in the window, we check if the script is already in the DOM. If it's present, we wait for it to load. Otherwise, we create a script element and append it to the body. Then we call our previous function - loadScript
.
To escape TypeScript errors, I have a declaration file that sets PaddleSdk
as a potential window property. I don't have all types for the library, but I fill it up as needed.
import { PaddleSdk } from "./PaddleSdk"
export declare global {
interface Window {
Paddle?: PaddleSdk
}
}
I store the react-query
cache in the local storage, but we don't want to cache the library. To achieve that, we exclude the paddle query from dehydration.
import { paddleQueryKey } from "membership/paddle/hooks/usePaddleSdk"
import { QueryClient, QueryKey } from "react-query"
import { createWebStoragePersistor } from "react-query/createWebStoragePersistor-experimental"
import { persistQueryClient } from "react-query/persistQueryClient-experimental"
import { MS_IN_DAY } from "utils/time"
const cacheTime = MS_IN_DAY * 5
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
cacheTime,
},
},
})
const localStoragePersistor = createWebStoragePersistor({
storage: window.localStorage,
})
const doNotPersistQueries: QueryKey[] = [paddleQueryKey]
persistQueryClient({
queryClient,
persistor: localStoragePersistor,
maxAge: cacheTime,
hydrateOptions: {},
dehydrateOptions: {
shouldDehydrateQuery: ({ queryKey }) => {
return !doNotPersistQueries.includes(queryKey)
},
},
})