Skip to content

Commit

Permalink
feat: add immer and query client
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Jul 13, 2024
1 parent 785dd6d commit b89e54a
Show file tree
Hide file tree
Showing 14 changed files with 478 additions and 5 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
"url": "https://"
},
"dependencies": {
"@tanstack/react-query": "5.51.1",
"clsx": "2.1.1",
"framer-motion": "11.3.2",
"immer": "10.1.1",
"jotai": "2.9.0",
"lodash-es": "4.17.21",
"ofetch": "1.3.4",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.24.1",
Expand Down
48 changes: 48 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/components/hooks/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './useBizQuery'
export * from './useDark'

Check failure on line 2 in src/components/hooks/common/index.ts

View workflow job for this annotation

GitHub Actions / build (14.x)

Cannot find module './useDark' or its corresponding type declarations.
export * from './usePrevious'
export * from './useRefValue'
export * from './useTitle'
83 changes: 83 additions & 0 deletions src/components/hooks/common/useBizQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import type {
InfiniteData,
QueryKey,
UseInfiniteQueryOptions,
UseInfiniteQueryResult,
UseQueryOptions,
UseQueryResult,
} from '@tanstack/react-query'
import type { DefinedQuery } from '~/utils/defineQuery'
import type { FetchError } from 'ofetch'

// TODO split normal define query and infinite define query for better type checking
export type SafeReturnType<T> = T extends (...args: any[]) => infer R
? R
: never

export type CombinedObject<T, U> = T & U
export function useAuthQuery<
TQuery extends DefinedQuery<QueryKey, any>,
TError = FetchError,
TQueryFnData = Awaited<ReturnType<TQuery['fn']>>,
TData = TQueryFnData,
>(
query: TQuery,
options: Omit<
UseQueryOptions<TQueryFnData, TError>,
'queryKey' | 'queryFn'
> = {},
): CombinedObject<
UseQueryResult<TData, TError>,
{ key: TQuery['key']; fn: TQuery['fn'] }
> {
// @ts-expect-error
return Object.assign(
{},
useQuery({
queryKey: query.key,
queryFn: query.fn,
enabled: options.enabled !== false,
...options,
}),
{
key: query.key,
fn: query.fn,
},
)
}

export function useAuthInfiniteQuery<
T extends DefinedQuery<any, any>,
E = FetchError,
FNR = Awaited<ReturnType<T['fn']>>,
R = FNR,
>(
query: T,
options: Omit<UseInfiniteQueryOptions<FNR, E>, 'queryKey' | 'queryFn'>,
): CombinedObject<
UseInfiniteQueryResult<InfiniteData<R>, FetchError>,
{ key: T['key']; fn: T['fn'] }
> {
// @ts-expect-error
return Object.assign(
{},
// @ts-expect-error
useInfiniteQuery<T, E>({
queryFn: query.fn,
queryKey: query.key,
enabled: options.enabled !== false,
...options,
}),
{
key: query.key,
fn: query.fn,
},
)
}

/**
* @deprecated use `useAuthQuery` instead
*/
export const useBizQuery = useAuthQuery
60 changes: 60 additions & 0 deletions src/components/hooks/common/useInputComposition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useCallback, useRef } from 'react'
import type { CompositionEventHandler } from 'react'

export const useInputComposition = (
props: Pick<
| React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
| React.DetailedHTMLProps<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
>,
'onKeyDown' | 'onCompositionEnd' | 'onCompositionStart'
>,
) => {
const { onKeyDown, onCompositionStart, onCompositionEnd } = props

const isCompositionRef = useRef(false)

const handleCompositionStart: CompositionEventHandler<any> = useCallback(
(e) => {
isCompositionRef.current = true
onCompositionStart?.(e)
},
[onCompositionStart],
)

const handleCompositionEnd: CompositionEventHandler<any> = useCallback(
(e) => {
isCompositionRef.current = false
onCompositionEnd?.(e)
},
[onCompositionEnd],
)

const handleKeyDown: React.KeyboardEventHandler<any> = useCallback(
(e) => {
onKeyDown?.(e)

if (isCompositionRef.current) {
e.stopPropagation()
return
}

if (e.key === 'Escape') {
e.currentTarget.blur()
e.preventDefault()
e.stopPropagation()
}
},
[onKeyDown],
)

return {
onCompositionEnd: handleCompositionEnd,
onCompositionStart: handleCompositionStart,
onKeyDown: handleKeyDown,
}
}
20 changes: 20 additions & 0 deletions src/components/hooks/common/useIsOnline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react'

export const useIsOnline = () => {
const [isOnline, setIsOnline] = useState(navigator.onLine)

useEffect(() => {
const handleOnline = () => setIsOnline(true)
const handleOffline = () => setIsOnline(false)

window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)

return () => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
}
}, [])

return isOnline
}
9 changes: 9 additions & 0 deletions src/components/hooks/common/usePrevious.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useEffect, useRef } from 'react'

export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>()
useEffect(() => {
ref.current = value
})
return ref.current
}
14 changes: 14 additions & 0 deletions src/components/hooks/common/useRefValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useLayoutEffect, useRef } from 'react'

export const useRefValue = <S>(
value: S,
): Readonly<{
current: Readonly<S>
}> => {
const ref = useRef<S>(value)

useLayoutEffect(() => {
ref.current = value
})
return ref
}
14 changes: 14 additions & 0 deletions src/components/hooks/common/useTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect, useRef } from 'react'

const titleTemplate = `%s | ${APP_NAME}`
export const useTitle = (title?: Nullable<string>) => {
const currentTitleRef = useRef(document.title)
useEffect(() => {
if (!title) return

document.title = titleTemplate.replace('%s', title)
return () => {
document.title = currentTitleRef.current
}
}, [title])
}
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare global {
export type Nullable<T> = T | null | undefined

export const APP_DEV_CWD: string
export const APP_NAME: string
}

export {}
12 changes: 8 additions & 4 deletions src/providers/root-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { QueryClientProvider } from '@tanstack/react-query'
import { LazyMotion, MotionConfig } from 'framer-motion'
import { Provider } from 'jotai'
import type { FC, PropsWithChildren } from 'react'

import { Toaster } from '~/components/ui/sonner'
import { jotaiStore } from '~/utils/jotai'
import { queryClient } from '~/utils/query-client'

import { StableRouterProvider } from './stable-router-provider'

Expand All @@ -18,10 +20,12 @@ export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
ease: 'easeInOut',
}}
>
<Provider store={jotaiStore}>
<StableRouterProvider />
{children}
</Provider>
<QueryClientProvider client={queryClient}>
<Provider store={jotaiStore}>
<StableRouterProvider />
{children}
</Provider>
</QueryClientProvider>
</MotionConfig>
<Toaster />
</LazyMotion>
Expand Down
Loading

0 comments on commit b89e54a

Please sign in to comment.