Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Improve documentation/logs around PostHogProvider #1675

Merged
merged 1 commit into from
Jan 23, 2025
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: 2 additions & 0 deletions react/src/components/__tests__/PostHogFeature.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ describe('PostHogFeature component', () => {
unobserve: () => null,
disconnect: () => null,
})

// eslint-disable-next-line compat/compat
window.IntersectionObserver = mockIntersectionObserver
})

Expand Down
68 changes: 45 additions & 23 deletions react/src/context/PostHogProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,63 @@ import posthogJs, { PostHogConfig } from 'posthog-js'
import React, { useMemo } from 'react'
import { PostHog, PostHogContext } from './PostHogContext'

export function PostHogProvider({
children,
client,
apiKey,
options,
}: {
children?: React.ReactNode
client?: PostHog | undefined
apiKey?: string | undefined
options?: Partial<PostHogConfig> | undefined
}) {
const posthog = useMemo(() => {
if (client && apiKey) {
console.warn(
'[PostHog.js] You have provided both a client and an apiKey to PostHogProvider. The apiKey will be ignored in favour of the client.'
)
}
type WithOptionalChildren<T> = T & { children?: React.ReactNode | undefined }

if (client && options) {
console.warn(
'[PostHog.js] You have provided both a client and options to PostHogProvider. The options will be ignored in favour of the client.'
)
}
/**
* Props for the PostHogProvider component.
* This is a discriminated union type that ensures mutually exclusive props:
*
* - If `client` is provided, `apiKey` and `options` must not be provided
* - If `apiKey` is provided, `client` must not be provided, and `options` is optional
*/
type PostHogProviderProps =
| { client: PostHog; apiKey?: never; options?: never }
| { apiKey: string; options?: Partial<PostHogConfig>; client?: never }

/**
* PostHogProvider is a React context provider for PostHog analytics.
* It can be initialized in two mutually exclusive ways:
*
* 1. By providing an existing PostHog `client` instance
* 2. By providing an `apiKey` (and optionally `options`) to create a new client
*
* These initialization methods are mutually exclusive - you must use one or the other,
* but not both simultaneously.
*/
export function PostHogProvider({ children, client, apiKey, options }: WithOptionalChildren<PostHogProviderProps>) {
const posthog = useMemo(() => {
if (client) {
if (apiKey) {
console.warn(
'[PostHog.js] You have provided both `client` and `apiKey` to `PostHogProvider`. `apiKey` will be ignored in favour of `client`.'
)
}

if (options) {
console.warn(
'[PostHog.js] You have provided both `client` and `options` to `PostHogProvider`. `options` will be ignored in favour of `client`.'
)
}

if (client.__loaded) {
console.warn('[PostHog.js] `client` was already loaded elsewhere. This may cause issues.')
}

return client
}

if (apiKey) {
if (posthogJs.__loaded) {
console.warn('[PostHog.js] was already loaded elsewhere. This may cause issues.')
console.warn('[PostHog.js] `posthog` was already loaded elsewhere. This may cause issues.')
}

posthogJs.init(apiKey, options)
return posthogJs
}

console.warn(
'[PostHog.js] No `apiKey` or `client` were provided to `PostHogProvider`. Using default global `window.posthog` instance. You must initialize it manually. This is not recommended behavior.'
)
return posthogJs
}, [client, apiKey])

Expand Down
20 changes: 13 additions & 7 deletions react/src/context/__tests__/PostHogContext.test.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react'
import { render } from '@testing-library/react'
import { PostHogContext, PostHogProvider } from '../'
import { PostHogProvider } from '..'

describe('usePostHogContext hook', () => {
describe('PostHogContext component', () => {
given(
'render',
() => () =>
Expand All @@ -18,11 +18,17 @@ describe('usePostHogContext hook', () => {
given.render()
})

it("should not error if a client instance can't be found in the context", () => {
// used to make sure it doesn't throw an error when no client is found e.g. nextjs
given('posthog', () => undefined)
console.error = jest.fn()
it("should not throw error if a client instance can't be found in the context", () => {
given('posthog', () => undefined) // it might not exist in SSR for example

expect(() => given.render())
// eslint-disable-next-line no-console
console.warn = jest.fn()

expect(() => given.render()).not.toThrow()

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'[PostHog.js] No `apiKey` or `client` were provided to `PostHogProvider`. Using default global `window.posthog` instance. You must initialize it manually. This is not recommended behavior.'
)
})
})
2 changes: 1 addition & 1 deletion react/src/context/__tests__/PostHogProvider.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { render } from '@testing-library/react'
import { PostHogProvider, PostHogContext } from '..'
import { PostHogProvider } from '..'

describe('PostHogProvider component', () => {
given(
Expand Down
2 changes: 2 additions & 0 deletions react/src/utils/type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export const isFunction = function (f: any): f is (...args: any[]) => any {
// eslint-disable-next-line posthog-js/no-direct-function-check
return typeof f === 'function'
}

export const isUndefined = function (x: unknown): x is undefined {
return x === void 0
}

export const isNull = function (x: unknown): x is null {
// eslint-disable-next-line posthog-js/no-direct-null-check
return x === null
Expand Down
Loading