Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | 81x 81x 81x 81x 81x 81x 27x 81x 29x 81x 5x 5x 5x 81x 22x 21x 21x 21x 21x 18x 18x 3x 81x 38x 22x 16x 16x 16x 16x 13x 11x 3x 1x 2x 2x 16x 16x 6x 81x 81x 81x 2x 81x 6x 81x 6x 75x 1x 81x 76x | import { FingerprintContext, FingerprintContextInterface, VisitorQueryResult } from './fingerprint-context'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { toError } from './utils/to-error'
import { assertIsDefined } from './utils/assert-is-defined'
import { areGetOptionsEqual } from './utils/get-options-cache-key'
import { GetOptions, GetResult } from '@fingerprint/agent'
export interface UseVisitorDataConfig {
/**
* Controls automatic visitor data fetching. When `true`, the hook fetches after mounting, and whenever the
* request options change. When changed from `false` to `true`, visitor data is initiated after the current render.
*/
immediate: boolean
}
export type UseVisitorDataOptions = GetOptions & UseVisitorDataConfig
export type UseVisitorDataReturn = VisitorQueryResult & {
/**
* Performs identification request to server and returns visitors data.
* */
getData: (getDataOptions?: GetOptions) => Promise<GetResult>
}
/**
* @example
* ```js
* const {
* // Request state
* data,
* isLoading,
* error,
* // A method to be called manually when the `immediate` field in the config is set to `false`:
* getData,
* } = useVisitorData({ immediate: false });
* ```
* Use the `useVisitorData` hook in your components to perform identification requests with the Fingerprint API. The returned object contains information about loading status, errors, and visitor.
*
* @param {UseVisitorDataOptions} options for the `agent.get()` request and for hook
*/
export function useVisitorData(
{ immediate, ...getOptions }: UseVisitorDataOptions = { immediate: true }
): UseVisitorDataReturn {
assertIsDefined(getOptions, 'getOptions')
const { getVisitorData } = useContext<FingerprintContextInterface>(FingerprintContext)
const [currentGetOptions, setCurrentGetOptions] = useState(getOptions)
const [currentImmediate, setCurrentImmediate] = useState(immediate)
const [queryState, setQueryState] = useState<VisitorQueryResult>({
isLoading: immediate,
data: undefined,
isFetched: false,
error: undefined,
})
const setLoading = () => {
setQueryState({
isLoading: true,
isFetched: false,
data: undefined,
error: undefined,
})
}
const setSuccess = (data: GetResult) => {
setQueryState({
isLoading: false,
isFetched: true,
data,
error: undefined,
})
}
const setFailure = (unknownError: unknown) => {
const error = toError(unknownError)
setQueryState({
isLoading: false,
isFetched: false,
data: undefined,
error,
})
return error
}
const getData = useCallback<UseVisitorDataReturn['getData']>(
async (params = {}) => {
assertIsDefined(params, 'getDataParams')
try {
setLoading()
const getDataOptions: GetOptions = {
...currentGetOptions,
...params,
}
const result = await getVisitorData(getDataOptions)
setSuccess(result)
return result
} catch (unknownError) {
throw setFailure(unknownError)
}
},
[currentGetOptions, getVisitorData]
)
/**
* When `immediate` is enabled, fetches visitor data on mount and whenever `getOptions` change.
* We don't reuse `getData` here because it sets loading state synchronously, which is not allowed
* inside an effect — `isLoading: true` is covered by the initial state and the render-phase reset below.
* The `ignore` flag prevents an outdated in-flight response from overwriting a newer request's state:
* https://react.dev/reference/react/useEffect#fetching-data-with-effects
*/
useEffect(() => {
if (!currentImmediate) {
return
}
let ignore = false
const fetchVisitorData = async () => {
try {
const result = await getVisitorData(currentGetOptions)
if (!ignore) {
setSuccess(result)
}
} catch (unknownError: unknown) {
if (ignore) {
return
}
const error = setFailure(unknownError)
console.error(`Failed to fetch visitor data automatically: ${error}`)
}
}
void fetchVisitorData()
return () => {
ignore = true
}
}, [currentImmediate, getVisitorData, currentGetOptions])
// When automatic-fetch inputs change, store them and reset to loading during render so the effect can start
// the request without synchronously setting state: https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
const didImmediateChange = currentImmediate !== immediate
const didGetOptionsChange =
!Object.is(currentGetOptions, getOptions) && !areGetOptionsEqual(currentGetOptions, getOptions)
if (didImmediateChange) {
setCurrentImmediate(immediate)
}
if (didGetOptionsChange) {
setCurrentGetOptions(getOptions)
}
if (immediate && (didImmediateChange || didGetOptionsChange)) {
setLoading()
} else if (didImmediateChange) {
// Disabling automatic fetching clears loading while the pending response is ignored.
setQueryState((state) => ({ ...state, isLoading: false }))
}
return useMemo(
() => ({
...queryState,
getData,
}),
[queryState, getData]
)
}
|