-
-
Save digitalParkour/5b97b6176ae1aaebc1a44d6b999dbe88 to your computer and use it in GitHub Desktop.
Sitecore Plugin - React Query Hydration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// EDIT FILE: /src/pages/[[...path]].tsx | |
// ... | |
import { HydrationBoundary } from '@tanstack/react-query'; | |
const SitecorePage = ({ | |
// ... | |
dehydratedState, | |
// ^ EXPOSE new page prop | |
}: SitecorePageProps): JSX.Element => { | |
// ... | |
return ( | |
<HydrationBoundary state={dehydratedState}> | |
{/* ^ ADD HydrationBoundary, set state to page prop resolved by page-props-factory plugin | |
This can be moved to _app.tsx to apply to all routes. | |
Kept here to support all Sitecore routes, allowing other routes to opt-in. */} | |
<ComponentPropsContext value={componentProps}> | |
{/* ... */} | |
</ComponentPropsContext> | |
</HydrationBoundary> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// EDIT FILE: /src/pages/_app.tsx | |
import ReactQueryClientProvider from '@/lib/providers/ReactQueryClientProvider'; | |
import '@/sass/main.scss'; | |
import { SitecorePageProps } from '@/types/props/page-props'; | |
import { I18nProvider } from 'next-localization'; | |
import type { AppProps } from 'next/app'; | |
import Bootstrap from 'src/Bootstrap'; | |
function App({ Component, pageProps }: AppProps<SitecorePageProps>): JSX.Element { | |
const { dictionary, locale, ...rest } = pageProps; | |
return ( | |
<> | |
<Bootstrap {...pageProps} /> | |
<I18nProvider lngDict={dictionary} locale={locale}> | |
<ReactQueryClientProvider> | |
{/* ^ ADD new QueryClientProvider HERE to cover every page route */} | |
<Component {...rest} /> | |
</ReactQueryClientProvider> | |
</I18nProvider> | |
</> | |
); | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// REPLACE NEW FILE: /src/components/SitecoreComponent.tsx | |
import ShowCatFact from '@/components/example/ShowCatFact'; | |
import { CatFactQueryKey, fetchCatFactAsync } from '@/components/example/fetch'; | |
import { GetStaticComponentProps } from '@sitecore-jss/sitecore-jss-nextjs'; | |
import { QueryClient, dehydrate } from '@tanstack/react-query'; | |
const FetchExample = (): JSX.Element => { | |
return <ShowCatFact />; | |
}; | |
export const getStaticProps: GetStaticComponentProps = async () => { | |
return { | |
props: { | |
// Condense any server-side queries to one line: | |
dehydratedState: await dehydrateQueriesAsync([[CatFactQueryKey], fetchCatFactAsync]), | |
}, | |
}; | |
}; | |
export default FetchExample; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/lib/utils/dehydrateQueries.ts | |
import { DehydratedState, QueryClient, dehydrate } from '@tanstack/react-query'; | |
export type QueryInput = [string[], () => unknown]; | |
/* | |
Fetch one or more queries in parallel server-side. | |
Returns dehydrated query client for page prop. | |
Input format - [queryKey, queryFn] pairs: | |
await dehydrateQueriesAsync([ | |
[['queryKey'], fetchFunction], | |
[['queryKey', var1, var2], () => myFetch(var1, var2)], | |
]) | |
Example usage: (in GetStaticProps/GetServerSideProps): | |
return { | |
props: { | |
dehydratedState: await dehydrateQueriesAsync([[CatFactQueryKey], fetchCatFactAsync]), | |
}, | |
}; | |
*/ | |
export const dehydrateQueriesAsync = async (queries: QueryInput[]): Promise<DehydratedState> => { | |
const queryClient = new QueryClient(); | |
await Promise.all( | |
queries.map((x): void => { | |
const [queryKey, queryFn] = x; | |
queryClient.prefetchQuery({ | |
queryKey, | |
queryFn, | |
}); | |
}) | |
); | |
return dehydrate(queryClient); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/lib/page-props-factory/plugins/dehydration-props.ts | |
import { DehydratedStateProps } from '@/types/props/component-props'; | |
import { SitecorePageProps } from '@/types/props/page-props'; | |
import { DehydratedState } from '@tanstack/react-query'; | |
import merge from 'ts-deepmerge'; | |
import { Plugin } from '..'; | |
/* | |
React Query Seamless Hydration Plugin | |
Allow component level GetServerSideProps|GetStaticProps to fetch and dehydrate data. | |
Aggregate here to page props level to send to <HydrationBoundary /> (** see [[...path]].tsx) | |
This passes server side data to client side cache, saving redundant client-side queries. | |
*/ | |
class DehydratedStatePlugin implements Plugin { | |
order = 3; // run after component-props plugin | |
async exec(props: SitecorePageProps) { | |
// Bubble up all component level dehydratedState props to this page level prop | |
let data: DehydratedState = { | |
mutations: [], | |
queries: [], | |
}; | |
// iterate all components declared in layout service response | |
if (props.componentProps) { | |
for (const p in props.componentProps) { | |
const thisProps = props.componentProps[p] as DehydratedStateProps; | |
// if has query state, merge it up | |
if (!!thisProps?.props?.dehydratedState) { | |
data = merge(data, thisProps.props?.dehydratedState); | |
} | |
} | |
} | |
// pass combined state to page props | |
props.dehydratedState = data; | |
return props; | |
} | |
} | |
// Export name must be <normalizedFileName>+"Plugin" | |
export const dehydrationPropPlugin = new DehydratedStatePlugin(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/components/example/fetch.ts | |
import { NativeDataFetcher } from '@sitecore-jss/sitecore-jss-nextjs'; | |
/* ==================== HTTP Fetch Example ================================ | |
/* | |
* A Fetch Method implements how to get the data. | |
* Here we will return a random cat fact from http endpoint | |
*/ | |
export const CatFactQueryKey = 'catFactQueryKey'; | |
export type TExampleCatFactResult = { | |
fact: string; | |
length: number; | |
}; | |
export async function fetchCatFactAsync(): Promise<TExampleCatFactResult> { | |
/* Use Sitecore Fetcher which has built-in debug logging */ | |
const fetcher = new NativeDataFetcher(); | |
const response = await fetcher.fetch<TExampleCatFactResult>('https://catfact.ninja/fact'); | |
return response.data; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// EDIT FILE: /src/lib/page-props.ts | |
// ... | |
import { DehydratedState } from '@tanstack/react-query'; | |
export type SitecorePageProps = { | |
// ... | |
dehydratedState?: DehydratedState; | |
// ^ ADD page prop | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/lib/providers/ReactQueryClientProvider.tsx | |
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | |
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; | |
import { useState } from 'react'; | |
type ReacQueryClientProviderProps = { | |
children: React.ReactElement; | |
}; | |
function ReacQueryClientProvider({ children }: ReacQueryClientProviderProps): JSX.Element { | |
// ReactQuery: use state for queryClient, which ensures each request has its own cache: | |
const [queryClient] = useState( | |
() => | |
new QueryClient({ | |
defaultOptions: { | |
queries: { | |
// Any global behavior defaults here: | |
refetchOnMount: false, | |
refetchOnReconnect: false, | |
refetchOnWindowFocus: false, | |
// With SSR, we usually want to set some default staleTime | |
// above 0 to avoid refetching immediately on the client | |
staleTime: Infinity, | |
gcTime: Infinity, | |
}, | |
}, | |
}) | |
); | |
return ( | |
<QueryClientProvider client={queryClient}> | |
{/* ^ ADD the QueryClient provider with our shared client (cache) instance */} | |
{children} | |
<ReactQueryDevtools /> | |
{/* ^ can ADD dev tools */} | |
</QueryClientProvider> | |
); | |
} | |
export default ReactQueryClientProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/components/example/ShowCatFact.tsx | |
import { useQuery } from '@tanstack/react-query'; | |
import { CatFactQueryKey, fetchCatFactAsync } from './fetch'; | |
const ShowCatFact = (): JSX.Element => { | |
const query = useQuery({ | |
queryKey: [CatFactQueryKey], | |
queryFn: fetchCatFactAsync, | |
}); | |
return ( | |
<div> | |
<h1>Cat Fact</h1> | |
<p>{query.isLoading ? '...' : query.data.fact}</p> | |
</div> | |
); | |
}; | |
export default ShowCatFact; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NEW FILE: /src/components/SitecoreComponent.tsx | |
import ShowCatFact from '@/components/example/ShowCatFact'; | |
import { CatFactQueryKey, fetchCatFactAsync } from '@/components/example/fetch'; | |
import { GetStaticComponentProps } from '@sitecore-jss/sitecore-jss-nextjs'; | |
import { QueryClient, dehydrate } from '@tanstack/react-query'; | |
const FetchExample = (): JSX.Element => { | |
return <ShowCatFact />; | |
}; | |
// This is called by Sitecore when Page route uses GetStaticProps, for SSG/ISR | |
export const getStaticProps: GetStaticComponentProps = async () => { | |
// 1. Instantiate QueryClient server side | |
const queryClient = new QueryClient(); | |
// 2. Run one or more queries to add results to cache | |
await queryClient.prefetchQuery({ | |
queryKey: [CatFactQueryKey], | |
queryFn: fetchCatFactAsync, | |
}); | |
return { | |
props: { | |
// 3. send dehydrated result cache for client side component props | |
dehydratedState: dehydrate(queryClient), | |
}, | |
}; | |
}; | |
export default FetchExample; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ShowCatFact = (): JSX.Element => { | |
const query = useQuery({ | |
queryKey: ['uniqueString'], // unique key to access fetched data | |
queryFn: fetchCatFactAsync, // function to (re)fetch data | |
}); | |
// ... | |
return <p>{query.isLoading ? '...' : query.data.fact}</p> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment