Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/use-segment/src/__tests__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const wrapper =
writeKey,
onError = e => log(e),
events = defaultEvents,
}: SegmentProviderProps) =>
}: SegmentProviderProps<typeof defaultEvents>) =>
({ children }: { children: ReactNode }) =>
(
<SegmentProvider writeKey={writeKey} onError={onError} events={events}>
Expand Down
46 changes: 29 additions & 17 deletions packages/use-segment/src/useSegment.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Analytics, AnalyticsBrowser } from '@segment/analytics-next'
import React, {
FunctionComponent,
ReactElement,
ReactNode,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react'

interface SegmentContextInteface {
type EventFunction = (...args: unknown[]) => Promise<void>
type Events = Record<string, (analytics?: Analytics) => EventFunction>

interface SegmentContextInterface<T extends Events = Events> {
analytics: Analytics | undefined
events: Record<string, unknown>
events: { [K in keyof T]: ReturnType<T[K]> }
writeKey?: string
onError?: (err: Error) => void
}
Expand All @@ -23,29 +25,32 @@ const initialContext = {
writeKey: undefined,
}

const SegmentContext = createContext<SegmentContextInteface>(initialContext)
const SegmentContext = createContext<SegmentContextInterface>(initialContext)

export const useSegment = (): SegmentContextInteface => {
const context = useContext(SegmentContext)
export function useSegment<T extends Events>(): SegmentContextInterface<T> {
// @ts-expect-error Here we force cast the generic onto the useContext because the context is a
// global variable and cannot be generic
const context = useContext<SegmentContextInterface<T>>(SegmentContext)
if (context === undefined) {
throw new Error('useSegment must be used within a SegmentProvider')
}

return context
}

export type SegmentProviderProps = {
export type SegmentProviderProps<T> = {
writeKey?: string
onError: (err: Error) => void
events: Record<string, unknown>
events: T
children: ReactNode
}

const SegmentProvider: FunctionComponent<SegmentProviderProps> = ({
function SegmentProvider<T extends Events>({
children,
writeKey,
onError,
events,
}): ReactElement => {
}: SegmentProviderProps<T>) {
const [analytics, setAnalytics] = useState<Analytics | undefined>(undefined)

useEffect(() => {
Expand All @@ -63,13 +68,20 @@ const SegmentProvider: FunctionComponent<SegmentProviderProps> = ({
}
}, [onError, writeKey])

const value = useMemo<SegmentContextInteface>(
() => ({
const value = useMemo<SegmentContextInterface<T>>(() => {
const curiedEvents = Object.entries(events).reduce(
(acc, [eventName, eventFn]) => ({
...acc,
[eventName]: eventFn(analytics),
}),
{},
) as { [K in keyof T]: ReturnType<T[K]> }

return {
analytics,
events,
}),
[analytics, events],
)
events: curiedEvents,
}
}, [analytics, events])

return (
<SegmentContext.Provider value={value}>{children}</SegmentContext.Provider>
Expand Down