Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install react-query #4361

Merged
merged 13 commits into from
Jul 22, 2024
82 changes: 82 additions & 0 deletions assets/js/dashboard/hooks/api-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useEffect } from "react"
import { useQueryClient, useInfiniteQuery } from "@tanstack/react-query"
import * as api from "../api"

const LIMIT = 100

/**
* A wrapper for the React Query library. Constructs the necessary options
* (including pagination config) to pass into the `useInfiniteQuery` hook.
*
* ### Required props
*
* @param {Array} key - The key under which the global "query" instance will live.
* Should be passed as a list of two elements - `[endpoint, { query }]`. The object
* can also contain additional values (such as `search`) to be used by:
* 1) React Query, to determine the uniqueness of the query instance
* 2) the `getRequestParams` function to build the request params.
*
* @param {Function} getRequestParams - A function that takes the `key` prop as an
* argument, and returns `[query, params]` which will be used by `queryFn` that
* actually calls the API.
*
* ### Optional props
*
* @param {Function} [afterFetchData] - A function to call after data has been fetched.
* Receives the API response as an argument.
*
* @param {Function} [afterFetchNextPage] - A function to call after the next page has
* been fetched. Receives the API response as an argument.
*/

export function useAPIClient(props) {
const {key, getRequestParams, afterFetchData, afterFetchNextPage} = props
const [endpoint] = key
const queryClient = useQueryClient()

const queryFn = async ({ pageParam, queryKey }) => {
const [query, params] = getRequestParams(queryKey)
params.limit = LIMIT
params.page = pageParam

const response = await api.get(endpoint, query, params)

if (pageParam === 1 && typeof afterFetchData === 'function') {
afterFetchData(response)
}

if (pageParam > 1 && typeof afterFetchNextPage === 'function') {
afterFetchNextPage(response)
}

return response.results
}

// During the cleanup phase, make sure only the first page of results
// is cached under any `queryKey` containing this endpoint.
useEffect(() => {
const key = [endpoint]
return () => {
queryClient.setQueriesData(key, (data) => {
if (data?.pages?.length) {
return {
pages: data.pages.slice(0, 1),
pageParams: data.pageParams.slice(0, 1),
}
}
})
}
}, [queryClient, endpoint])

const getNextPageParam = (lastPageResults, _, lastPageIndex) => {
return lastPageResults.length === LIMIT ? lastPageIndex + 1 : null
}
const initialPageParam = 1

return useInfiniteQuery({
queryKey: key,
queryFn,
getNextPageParam,
initialPageParam
})
}
95 changes: 55 additions & 40 deletions assets/js/dashboard/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ import FilterModal from './stats/modals/filter-modal'
import QueryContextProvider from './query-context';
import { useSiteContext } from './site-context';

import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false
}
}
})

function ScrollToTop() {
const location = useLocation();

Expand All @@ -30,45 +43,47 @@ function ScrollToTop() {
export default function Router() {
const site = useSiteContext()
return (
<BrowserRouter basename={site.shared ? `/share/${encodeURIComponent(site.domain)}` : encodeURIComponent(site.domain)}>
<QueryContextProvider>
<Route path="/">
<ScrollToTop />
<Dashboard />
<Switch>
<Route exact path={["/sources", "/utm_mediums", "/utm_sources", "/utm_campaigns", "/utm_contents", "/utm_terms"]}>
<SourcesModal />
</Route>
<Route exact path="/referrers/Google">
<GoogleKeywordsModal site={site} />
</Route>
<Route exact path="/referrers/:referrer">
<ReferrersDrilldownModal />
</Route>
<Route path="/pages">
<PagesModal />
</Route>
<Route path="/entry-pages">
<EntryPagesModal />
</Route>
<Route path="/exit-pages">
<ExitPagesModal />
</Route>
<Route exact path={["/countries", "/regions", "/cities"]}>
<LocationsModal />
</Route>
<Route path="/custom-prop-values/:prop_key">
<PropsModal />
</Route>
<Route path="/conversions">
<ConversionsModal />
</Route>
<Route path={["/filter/:field"]}>
<FilterModal site={site} />
</Route>
</Switch>
</Route>
</QueryContextProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<BrowserRouter basename={site.shared ? `/share/${encodeURIComponent(site.domain)}` : encodeURIComponent(site.domain)}>
<QueryContextProvider>
<Route path="/">
<ScrollToTop />
<Dashboard />
<Switch>
<Route exact path={["/sources", "/utm_mediums", "/utm_sources", "/utm_campaigns", "/utm_contents", "/utm_terms"]}>
<SourcesModal />
</Route>
<Route exact path="/referrers/Google">
<GoogleKeywordsModal site={site} />
</Route>
<Route exact path="/referrers/:referrer">
<ReferrersDrilldownModal />
</Route>
<Route path="/pages">
<PagesModal />
</Route>
<Route path="/entry-pages">
<EntryPagesModal />
</Route>
<Route path="/exit-pages">
<ExitPagesModal />
</Route>
<Route exact path={["/countries", "/regions", "/cities"]}>
<LocationsModal />
</Route>
<Route path="/custom-prop-values/:prop_key">
<PropsModal />
</Route>
<Route path="/conversions">
<ConversionsModal />
</Route>
<Route path={["/filter/:field"]}>
<FilterModal site={site} />
</Route>
</Switch>
</Route>
</QueryContextProvider>
</BrowserRouter>
</QueryClientProvider>
);
}
Loading
Loading