Skip to content

Commit

Permalink
perf: use tanstack query to cache attribute and useDocument requests
Browse files Browse the repository at this point in the history
  • Loading branch information
soofstad committed May 31, 2024
1 parent 4040605 commit 3865373
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 88 deletions.
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@development-framework/dm-core": "^1.3.3",
"@development-framework/dm-core-plugins": "^1.3.3",
"@tanstack/react-query": "^5.37.1",
"plotly.js": "^2.18.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/dm-core/src/ApplicationContext.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React, {
Dispatch,
ReactNode,
Expand All @@ -20,6 +21,7 @@ const DEFAULT_ROLE: TRole = {
authServerRoleName: 'anonymous',
label: 'Anonymous',
}
const queryClient = new QueryClient()
export const ApplicationContext = React.createContext<
| {
name: string
Expand Down Expand Up @@ -153,7 +155,9 @@ export const DMApplicationProvider = (props: {
loading,
}}
>
{props.children}
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
<ToastContainer />
</ApplicationContext.Provider>
)
Expand Down
66 changes: 37 additions & 29 deletions packages/dm-core/src/components/ViewCreator/ViewCreator.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Typography } from '@equinor/eds-core-react'
import { AxiosResponse } from 'axios'
import React, { useEffect, useMemo, useState } from 'react'
import { EntityView, Loading, TAttribute, useApplication } from '../../index'
import { useQuery } from '@tanstack/react-query'
import { AxiosError } from 'axios'
import React, { useMemo, useState } from 'react'
import {
EntityView,
ErrorResponse,
Loading,
TAttribute,
useApplication,
} from '../../index'
import {
IUIPlugin,
TInlineRecipeViewConfig,
Expand Down Expand Up @@ -40,39 +47,40 @@ type TViewCreator = Omit<IUIPlugin, 'type'> & {
export const ViewCreator = (props: TViewCreator): React.ReactElement => {
const { idReference, viewConfig, onOpen, onSubmit, onChange } = props
const { dmssAPI } = useApplication()
const [isLoading, setIsLoading] = useState<boolean>(true)
const [error, setError] = useState<Error>()
const [attribute, setAttribute] = useState<TAttribute>()
const [directAddress, setDirectAddress] = useState<string>()

const reference = useMemo(
() => getTarget(idReference, viewConfig),
[idReference, viewConfig]
)
const queryKeys = ['attributes', reference, viewConfig.resolve]

useEffect(() => {
dmssAPI
.attributeGet({
address: reference,
resolve: props.viewConfig.resolve,
})
.then((response: AxiosResponse) => {
setAttribute(response.data.attribute)
setDirectAddress(response.data.address)
})
.catch((error) => setError(error))
.finally(() => setIsLoading(false))
}, [reference])
const { isPending, data } = useQuery<{
address: string
attribute: TAttribute
}>({
staleTime: 5 * 1000,
refetchOnMount: false,
queryKey: queryKeys,
queryFn: () =>
dmssAPI
.attributeGet({
address: reference,
resolve: props.viewConfig.resolve,
})
.then((response: any) => response.data)
.catch((error: AxiosError<ErrorResponse>) => setError(error)),
})

if (isLoading || !directAddress) return <Loading />
if (isPending || !data?.address) return <Loading />
if (error)
return (
<Typography>
Could not find attribute for document with id {reference} (
{error.message})
</Typography>
)
if (attribute === undefined)
if (data.attribute === undefined)
throw new Error('Unable to find type and dimensions for view')

if (viewConfig === undefined)
Expand All @@ -82,8 +90,8 @@ export const ViewCreator = (props: TViewCreator): React.ReactElement => {
if (isInlineRecipeViewConfig(viewConfig)) {
return (
<InlineRecipeView
idReference={directAddress}
type={attribute.attributeType}
idReference={data.address}
type={data.attribute.attributeType}
viewConfig={viewConfig}
onOpen={onOpen}
onSubmit={onSubmit}
Expand All @@ -95,11 +103,11 @@ export const ViewCreator = (props: TViewCreator): React.ReactElement => {
if (isReferenceViewConfig(viewConfig)) {
return (
<EntityView
type={attribute.attributeType}
idReference={directAddress}
type={data.attribute.attributeType}
idReference={data.address}
recipeName={viewConfig.recipe}
onOpen={onOpen}
dimensions={attribute.dimensions}
dimensions={data.attribute.dimensions}
showRefreshButton={viewConfig.showRefreshButton}
onSubmit={onSubmit}
onChange={onChange}
Expand All @@ -108,10 +116,10 @@ export const ViewCreator = (props: TViewCreator): React.ReactElement => {
} else if (isViewConfig(viewConfig)) {
return (
<EntityView
idReference={directAddress}
type={attribute.attributeType}
idReference={data.address}
type={data.attribute.attributeType}
onOpen={onOpen}
dimensions={attribute.dimensions}
dimensions={data.attribute.dimensions}
showRefreshButton={viewConfig.showRefreshButton}
onSubmit={onSubmit}
onChange={onChange}
Expand Down
129 changes: 71 additions & 58 deletions packages/dm-core/src/hooks/useDocument.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AxiosError } from 'axios'
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { Dispatch, SetStateAction, useState } from 'react'
import { ErrorResponse } from '../services'

import { toast } from 'react-toastify'
import { useApplication } from '../ApplicationContext'

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
interface IUseDocumentReturnType<T> {
document: T | null
isLoading: boolean
Expand Down Expand Up @@ -55,68 +56,80 @@ export function useDocument<T>(
depth?: number | undefined,
notify: boolean = true
): IUseDocumentReturnType<T> {
const [document, setDocument] = useState<T | null>(null)
const [isLoading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<ErrorResponse | null>(null)
const { dmssAPI } = useApplication()
const [errorResponse, setErrorResponse] = useState<ErrorResponse | null>(null)
const queryClient = useQueryClient()

useEffect(() => {
setLoading(true)
const documentDepth: number = depth ?? 0
if (documentDepth < 0 || documentDepth > 999)
throw new Error('Depth must be a positive number < 999')
dmssAPI
.documentGet({
address: idReference,
depth: documentDepth,
})
.then((response: any) => {
const data = response.data
setDocument(data)
setError(null)
})
.catch((error: AxiosError<ErrorResponse>) => {
console.error(error)
if (notify)
toast.error(
'Unable to retrieve document, with message: ' +
error.response?.data.message ?? error.message
const queryKeys = ['documents', idReference, depth]
const documentDepth: number = depth ?? 0
if (documentDepth < 0 || documentDepth > 999)
throw new Error('Depth must be a positive number < 999')

const { isPending, data } = useQuery({
staleTime: 5 * 1000,
refetchOnMount: false,
queryKey: queryKeys,
queryFn: () =>
dmssAPI
.documentGet({
address: idReference,
depth: documentDepth,
})
.then((response: any) => response.data)
.catch((error: AxiosError<ErrorResponse>) => {
console.error(error)
if (notify)
toast.error(
'Unable to retrieve document, with message: ' +
error.response?.data.message ?? error.message
)
setErrorResponse(
error.response?.data || { message: error.name, data: error }
)
setError(error.response?.data || { message: error.name, data: error })
})
.finally(() => setLoading(false))
}, [idReference, depth])
}),
})

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function updateDocument(
newDocument: T,
notify: boolean = true,
partialUpdate: boolean = false,
throwError: boolean = false
): Promise<void> {
setLoading(true)
return dmssAPI
.documentUpdate({
idAddress: idReference,
data: JSON.stringify(newDocument),
partialUpdate: partialUpdate,
})
.then((response: any) => {
const data = response.data.data
setDocument(data)
setError(null)
if (notify) toast.success('Document updated')
})
.catch((error: AxiosError<ErrorResponse>) => {
console.error(error)
if (notify) toast.error(error.response?.data.message ?? error.message)
setError(error.response?.data || { message: error.name, data: error })
if (throwError) {
const mutation = useMutation({
mutationFn: ({
newDocument,
partialUpdate,
notify,
throwError,
}: {
newDocument: T
notify?: boolean
partialUpdate?: boolean
throwError?: boolean
}) =>
dmssAPI
.documentUpdate({
idAddress: idReference,
data: JSON.stringify(newDocument),
partialUpdate: partialUpdate,
})
.then((response: any) => {
queryClient.setQueryData(queryKeys, response.data.data)
setErrorResponse(null)
if (notify) toast.success('Document updated')
})
.catch((error: AxiosError<ErrorResponse>) => {
console.error(error)
if (notify) toast.error(error.response?.data.message ?? error.message)
setErrorResponse(
error.response?.data || { message: error.name, data: error }
)
if (throwError) {
throw new Error(JSON.stringify(error, null, 2))
}
})
.finally(() => setLoading(false))
}
}),
})

return { document, isLoading, updateDocument, error, setError }
return {
document: data || null,
isLoading: isPending,
updateDocument: (newDocument: T, notify, partialUpdate, throwError) =>
mutation.mutateAsync({ newDocument, notify, partialUpdate, throwError }),
error: errorResponse,
setError: setErrorResponse
}
}

0 comments on commit 3865373

Please sign in to comment.