Skip to content

Commit 80c2514

Browse files
chambo-ephilibea
authored andcommitted
feat: generic segment (#590)
* chore: generic segment * fix: fmt
1 parent c8909b9 commit 80c2514

File tree

4 files changed

+37
-22
lines changed

4 files changed

+37
-22
lines changed

packages/use-segment/src/__tests__/events/pageVisited.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { Analytics } from '@segment/analytics-next'
22
import { PageType, ProductCategory } from './types'
33

44

5-
const pageVisited = (analytics: Analytics) => async (
5+
const pageVisited = (analytics?: Analytics) => async (
66
pageType: PageType,
77
organizationId: string,
88
productCategory?: ProductCategory
99
): Promise<void> => {
10-
await analytics.page(
10+
await analytics?.page(
1111
{
1212
page_type: pageType,
1313
product_category: productCategory,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type PageType = 'Docs' | 'Blog' | 'Main'
2+
export type ProductCategory = 'Dedibox' | 'Elements' | 'Datacenter'

packages/use-segment/src/__tests__/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import * as defaultEvents from './events'
66

77
const { log } = console
88

9+
type DefaultEvents = typeof defaultEvents
10+
911
const wrapper =
1012
({
1113
writeKey,
1214
onError = e => log(e),
1315
events = defaultEvents,
14-
}: SegmentProviderProps) =>
16+
}: SegmentProviderProps<typeof defaultEvents>) =>
1517
({ children }: { children: ReactNode }) =>
1618
(
1719
<SegmentProvider writeKey={writeKey} onError={onError} events={events}>
@@ -30,14 +32,13 @@ describe('segment hook', () => {
3032
})
3133

3234
it('useSegment should not load without key', () => {
33-
const { result } = renderHook(() => useSegment(), {
35+
const { result } = renderHook(() => useSegment<DefaultEvents>(), {
3436
wrapper: wrapper({
3537
events: defaultEvents,
3638
onError: e => log(e),
3739
writeKey: undefined,
3840
}),
3941
})
40-
console.log(result.current)
4142
expect(result.current.analytics).toBe(undefined)
4243
})
4344
})

packages/use-segment/src/useSegment.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { Analytics, AnalyticsBrowser } from '@segment/analytics-next'
22
import React, {
3-
FunctionComponent,
4-
ReactElement,
3+
ReactNode,
54
createContext,
65
useContext,
76
useEffect,
87
useMemo,
98
useState,
109
} from 'react'
1110

12-
interface SegmentContextInteface {
11+
type EventFunction = (...args: unknown[]) => Promise<void>
12+
type Events = Record<string, (analytics?: Analytics) => EventFunction>
13+
14+
interface SegmentContextInterface<T extends Events = Events> {
1315
analytics: Analytics | undefined
14-
events: Record<string, unknown>
16+
events: { [K in keyof T]: ReturnType<T[K]> }
1517
writeKey?: string
1618
onError?: (err: Error) => void
1719
}
@@ -23,29 +25,32 @@ const initialContext = {
2325
writeKey: undefined,
2426
}
2527

26-
const SegmentContext = createContext<SegmentContextInteface>(initialContext)
28+
const SegmentContext = createContext<SegmentContextInterface>(initialContext)
2729

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

3438
return context
3539
}
3640

37-
export type SegmentProviderProps = {
41+
export type SegmentProviderProps<T> = {
3842
writeKey?: string
3943
onError: (err: Error) => void
40-
events: Record<string, unknown>
44+
events: T
45+
children: ReactNode
4146
}
4247

43-
const SegmentProvider: FunctionComponent<SegmentProviderProps> = ({
48+
function SegmentProvider<T extends Events>({
4449
children,
4550
writeKey,
4651
onError,
4752
events,
48-
}): ReactElement => {
53+
}: SegmentProviderProps<T>) {
4954
const [analytics, setAnalytics] = useState<Analytics | undefined>(undefined)
5055

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

66-
const value = useMemo<SegmentContextInteface>(
67-
() => ({
71+
const value = useMemo<SegmentContextInterface<T>>(() => {
72+
const curiedEvents = Object.entries(events).reduce(
73+
(acc, [eventName, eventFn]) => ({
74+
...acc,
75+
[eventName]: eventFn(analytics),
76+
}),
77+
{},
78+
) as { [K in keyof T]: ReturnType<T[K]> }
79+
80+
return {
6881
analytics,
69-
events,
70-
}),
71-
[analytics, events],
72-
)
82+
events: curiedEvents,
83+
}
84+
}, [analytics, events])
7385

7486
return (
7587
<SegmentContext.Provider value={value}>{children}</SegmentContext.Provider>

0 commit comments

Comments
 (0)