-
Notifications
You must be signed in to change notification settings - Fork 48
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
Add survey support for React Native #333
Changes from 4 commits
a19b9fc
0519932
37c1115
c3e3b2c
3f348a2
0825fb6
00ba10e
57f1778
e8ef17a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,175 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import React, { useEffect, useMemo, useState } from 'react' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { dismissedSurveyEvent, sendSurveyShownEvent } from './components/Surveys' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getActiveMatchingSurveys } from './getActiveMatchingSurveys' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useSurveyStorage } from './useSurveyStorage' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useActivatedSurveys } from './useActivatedSurveys' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { SurveyModal } from './components/SurveyModal' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { defaultSurveyAppearance, getContrastingTextColor, SurveyAppearanceTheme } from './surveys-utils' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Survey, SurveyAppearance } from './posthog-surveys-types' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { usePostHog } from '../hooks/usePostHog' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useFeatureFlags } from '../hooks/useFeatureFlags' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type ActiveSurveyContextType = { survey: Survey; onShow: () => void; onClose: (submitted: boolean) => void } | undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ActiveSurveyContext = React.createContext<ActiveSurveyContextType>(undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const useActiveSurvey = (): ActiveSurveyContextType => React.useContext(ActiveSurveyContext) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type FeedbackSurveyHook = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
survey: Survey | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
showSurveyModal: () => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hideSurveyModal: () => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const FeedbackSurveyContext = React.createContext< | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
surveys: Survey[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
activeSurvey: Survey | undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setActiveSurvey: React.Dispatch<React.SetStateAction<Survey | undefined>> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
>(undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const useFeedbackSurvey = (selector: string): FeedbackSurveyHook | undefined => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const context = React.useContext(FeedbackSurveyContext) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const survey = context?.surveys.find( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(survey) => survey.type === 'widget' && survey.appearance?.widgetSelector === selector | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!context || !survey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
survey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
showSurveyModal: () => context.setActiveSurvey(survey), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hideSurveyModal: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (context.activeSurvey === survey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
context.setActiveSurvey(undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export type PostHogSurveyProviderProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Whether to show the default survey modal when there is an active survey. (Default true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* If false, you can call useActiveSurvey and render survey content yourself. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
**/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
automaticSurveyModal?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* The default appearance for surveys when not specified in PostHog. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultSurveyAppearance?: SurveyAppearance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* If true, PosHog appearance will be ignored and defaultSurveyAppearance is always used. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
overrideAppearanceWithDefault?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
children: React.ReactNode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function PostHogSurveyProvider(props: PostHogSurveyProviderProps): JSX.Element { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const posthog = usePostHog() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { seenSurveys, setSeenSurvey, lastSeenSurveyDate, setLastSeenSurveyDate } = useSurveyStorage() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [surveys, setSurveys] = useState<Survey[]>([]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [activeSurvey, setActiveSurvey] = useState<Survey | undefined>(undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const activatedSurveys = useActivatedSurveys(posthog, surveys) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//TODO Why is this untyped? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const flags: Record<string, string | boolean> | undefined = useFeatureFlags(posthog) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Load surveys once | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
posthog | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.fetchSurveys() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.then(setSurveys) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.catch((error: unknown) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
posthog.capture('PostHogSurveyProvider failed to fetch surveys', { error }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [posthog]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will likely change since we are adding a remote config API (WIP), and we'll only load surveys if there are surveys to be loaded. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved the fetching of surveys to Core. If I understand this comment correctly it sounds like it'd be best to check this remote config, and possibly cache the fetched surveys to prevent re-fetch, in core as well later on. I think this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, i will do the remote config next week so we can cherry pick from my changes |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Whenever state changes and there's no active survey, check if there is a new survey to show | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (activeSurvey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const activeSurveys = getActiveMatchingSurveys( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
surveys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
flags ?? {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
seenSurveys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
activatedSurveys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lastSeenSurveyDate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const popoverSurveys = activeSurveys.filter((survey) => survey.type === 'popover') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const popoverSurveyQueue = sortSurveysByAppearanceDelay(popoverSurveys) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (popoverSurveyQueue.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setActiveSurvey(popoverSurveyQueue[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [activeSurvey, flags, surveys, seenSurveys, activatedSurveys, lastSeenSurveyDate, props.automaticSurveyModal]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+91
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: props.automaticSurveyModal is in dependency array but not used in effect. This causes unnecessary effect runs.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Merge survey appearance so that components and hooks can use a consistent model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const surveyAppearance = useMemo<SurveyAppearanceTheme>(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (props.overrideAppearanceWithDefault || !activeSurvey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...defaultSurveyAppearance, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(props.defaultSurveyAppearance ?? {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...defaultSurveyAppearance, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(props.defaultSurveyAppearance ?? {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(activeSurvey.appearance ?? {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// If submitButtonColor is set by PostHog, ensure submitButtonTextColor is also set to contrast | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(activeSurvey.appearance?.submitButtonColor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
submitButtonTextColor: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
activeSurvey.appearance.submitButtonTextColor ?? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
getContrastingTextColor(activeSurvey.appearance.submitButtonColor), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [activeSurvey, props.defaultSurveyAppearance, props.overrideAppearanceWithDefault]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const activeContext = useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!activeSurvey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
survey: activeSurvey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onShow: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sendSurveyShownEvent(activeSurvey, posthog) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setLastSeenSurveyDate(new Date()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClose: (submitted: boolean) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setSeenSurvey(activeSurvey.id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setActiveSurvey(undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!submitted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dismissedSurveyEvent(activeSurvey, posthog) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [activeSurvey, posthog, setLastSeenSurveyDate, setSeenSurvey]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Modal is shown for PopOver surveys or if automaticSurveyModal is true, and for all widget surveys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// because these would have been invoked by the useFeedbackSurvey hook's showSurveyModal() method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const shouldShowModal = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
activeContext && (props.automaticSurveyModal !== false || activeContext.survey.type === 'widget') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ActiveSurveyContext.Provider value={activeContext}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<FeedbackSurveyContext.Provider value={{ surveys, activeSurvey, setActiveSurvey }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{props.children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{shouldShowModal && <SurveyModal appearance={surveyAppearance} {...activeContext} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</FeedbackSurveyContext.Provider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</ActiveSurveyContext.Provider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function sortSurveysByAppearanceDelay(surveys: Survey[]): Survey[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return surveys.sort( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(a, b) => (a.appearance?.surveyPopupDelaySeconds ?? 0) - (b.appearance?.surveyPopupDelaySeconds ?? 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+169
to
+173
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: sortSurveysByAppearanceDelay mutates the original array due to Array.sort(). Consider using [...surveys].sort() to avoid side effects. |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,122 @@ | ||||||||||
# PostHog React Native Surveys | ||||||||||
|
||||||||||
A port of survey UI and hooks from [poshog-js](https://github.com/PostHog/posthog-js) to React Native. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: Typo in URL - 'poshog-js' should be 'posthog-js'
Suggested change
|
||||||||||
|
||||||||||
Adds support for popover and manually triggered surveys when using posthog-react-native. | ||||||||||
|
||||||||||
## Usage | ||||||||||
|
||||||||||
Add `PostHogSurveyProvider` to your app anywhere inside `PostHogProvider`. This component fetches surveys and provides hooks described below. It also acts as the root for where popover surveys are rendered. | ||||||||||
|
||||||||||
```tsx | ||||||||||
<PostHogProvider /*... your config ...*/> | ||||||||||
<PostHogSurveyProvider>{children}</PostHogSurveyProvider> | ||||||||||
</PostHogProvider> | ||||||||||
``` | ||||||||||
|
||||||||||
Survey appearance respects settings in the Customization section of the survey setup. | ||||||||||
If you want to override this so that all surveys use your app theme, you can set | ||||||||||
|
||||||||||
```tsx | ||||||||||
<PostHogProvider /*... your config ...*/> | ||||||||||
<PostHogSurveyProvider | ||||||||||
overrideAppearanceWithDefault={true} | ||||||||||
defaultSurveyAppearance={{ ... }}> | ||||||||||
{children} | ||||||||||
</PostHogSurveyProvider> | ||||||||||
</PostHogProvider> | ||||||||||
``` | ||||||||||
|
||||||||||
### Feedback Button Surveys | ||||||||||
|
||||||||||
While this library doesn't provide the beta Feedback Widget UI, you can manually trigger a survey using your own button by using the `useFeedbackSurvey` hook. | ||||||||||
|
||||||||||
When creating your survey in PostHog set: | ||||||||||
|
||||||||||
- Presentation = Feedback button | ||||||||||
- Customization -> Feedback button type = Custom | ||||||||||
- Customization -> Class or ID selector = The value you pass to `useFeedbackSurvey` | ||||||||||
|
||||||||||
```ts | ||||||||||
export function FeedbackButton() { | ||||||||||
const feedbackSurvey = useFeedbackSurvey('MySurveySelector') | ||||||||||
|
||||||||||
const onPress = () => { | ||||||||||
if (feedbackSurvey) { | ||||||||||
feedbackSurvey.showSurveyModal() | ||||||||||
} else { | ||||||||||
// Your fallback in case this survey doesn't exist | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
return <Button onPress={onPress} title="Send Feedback" /> | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
### Custom Components | ||||||||||
|
||||||||||
By default, popover surveys are shown automatically. You can disable this by setting `automaticSurveyModal={false}`. | ||||||||||
|
||||||||||
The hook `useActiveSurvey` will return the survey that should currently be displayed. | ||||||||||
You can also import the `<Questions>` component directly and pass your own survey appearance if you'd like to reuse the survey content in your own modal or screen. | ||||||||||
|
||||||||||
```ts | ||||||||||
import { useActiveSurvey, type SurveyAppearance } from 'posthog-react-native' | ||||||||||
|
||||||||||
const appearance: SurveyAppearance = { | ||||||||||
// ... Your theme here | ||||||||||
} | ||||||||||
|
||||||||||
export function SurveyScreen() { | ||||||||||
const activeSurvey = useActiveSurvey() | ||||||||||
if (!activeSurvey) return null | ||||||||||
|
||||||||||
const { survey, onShow, onClose } = activeSurvey | ||||||||||
|
||||||||||
const onSubmit = () => { | ||||||||||
// e.g. you might show your own thank you message here | ||||||||||
onClose() | ||||||||||
} | ||||||||||
|
||||||||||
useEffect(() => { | ||||||||||
// Call this once when the survey is show | ||||||||||
onShow() | ||||||||||
Comment on lines
+82
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: Typo in comment - 'show' should be 'shown'
Suggested change
|
||||||||||
}, [onShow]) | ||||||||||
|
||||||||||
return ( | ||||||||||
<View style={styles.surveyScreen}> | ||||||||||
<Questions | ||||||||||
survey={survey} | ||||||||||
onSubmit={onSubmit} | ||||||||||
appearance={appearance} | ||||||||||
styleOverride={styles.surveyScrollViewStyle} | ||||||||||
/> | ||||||||||
</View> | ||||||||||
) | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
## Supported Features | ||||||||||
|
||||||||||
| Feature | Support | | ||||||||||
| --------------------------------- | ---------------------------------- | | ||||||||||
| **Questions** | | | ||||||||||
| All question types | ✅ | | ||||||||||
| Multi-question surveys | ✅ | | ||||||||||
| Confirmation message | ✅ When using default modal UI | | ||||||||||
| **Feedback Button Presentation** | | | ||||||||||
| Custom feedback button | ✅ Via `useFeedbackSurvey` hook | | ||||||||||
| Pre-built feedback tab | ❌ | | ||||||||||
| **Customization / Appearance** | _When using default modal UI_ | | ||||||||||
| Set colors in PostHog Dashboard | ✅ Or override with your app theme | | ||||||||||
| Shuffle Questions | ✅ | | ||||||||||
| PostHog branding | ❌ Always off | | ||||||||||
| Delay popup after page load | ✅ | | ||||||||||
| Position config | ❌ Always bottom center | | ||||||||||
| **Display conditions** | | | ||||||||||
| Feature flag & property targeting | ✅ | | ||||||||||
| URL Targeting | ❌ | | ||||||||||
| CSS Selector Matches | ❌ | | ||||||||||
| Survey Wait period | ✅ | | ||||||||||
| Event Triggers | ✅ | | ||||||||||
| Action Triggers | ❌ | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO add peer dependency for safe area context