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

Add survey support for React Native #333

Merged

Conversation

ian-craig
Copy link

Problem

#110 posthog-react-native doesn't have built in support for surveys

Changes

This PR ports most of the survey logic from posthog-js into react native.

I've re-implemented most of the state handling and persistence to make it more appropriate for React Native and built on top of posthog-react-native.

See surveys/Readme.md for details on the hooks and config options I've added. I was originally going to post this as a separate library to start which is why I've split out <PostHogSurveyProvider> etc., but we can probably merge some of that in to the main provider.

Release info Sub-libraries affected

Bump level

  • Major
  • Minor
  • Patch

Should all be backwards compatible, with the possible exception of adding a peer dependency for SVGs. We can look into what the best approach for this is.

Libraries affected

  • All of them
  • posthog-web
  • posthog-node
  • posthog-react-native

Changelog notes

  • TODO

@marandaneto
Copy link
Member

@ian-craig thanks a lot.
just back from the holidays, I'll catch up with a few things before and I should be able to test it/review it by EoW, thanks for doing this!

@ian-craig
Copy link
Author

@marandaneto any chance you've had some time to take a look at this? Thanks!

@marandaneto
Copy link
Member

@marandaneto any chance you've had some time to take a look at this? Thanks!

hello, yes, I started looking into it already, expect a thoughtful review today or Monday latest =) sorry the delay!

flex: 1,
alignItems: 'center',
justifyContent: 'flex-end',
marginVertical: 40, // TODO Plus safe area?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I've added an "Optional" dependency on safe area insets like you've done with other libraries, but I'm not sure how I feel about it in this case.

The trouble is we're also dependent on the developer having a <SafeAreaProvider> outside where they put the <PostHogProvider>. I've put a fallback to some default insets if not, but it still seems like a hidden gotcha.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can document/add to code snippets, most people will just copy-pasta anyway

Comment on lines 82 to 90
// Load surveys once
useEffect(() => {
posthog
.fetchSurveys()
.then(setSurveys)
.catch((error: unknown) => {
posthog.capture('PostHogSurveyProvider failed to fetch surveys', { error })
})
}, [posthog])
Copy link
Member

Choose a reason for hiding this comment

The 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.
That means we'll be calling another API when the SDK starts.

Copy link
Author

Choose a reason for hiding this comment

The 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 surveys state would still be needed though as it's allowing everything else in this provider to update once the surveys finish loading.

Copy link
Member

Choose a reason for hiding this comment

The 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

@@ -22,6 +22,7 @@ import {
} from './types'
import { withReactNativeNavigation } from './frameworks/wix-navigation'
import { OptionalReactNativeSessionReplay } from './optional/OptionalSessionReplay'
import { Survey, SurveyResponse } from '../../posthog-core/src/posthog-surveys-types'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO Remove this import now the fetch method has moved to core

@@ -33,6 +33,7 @@
"react-native": "^0.69.1",
"react-native-device-info": "^10.3.0",
"react-native-navigation": "^6.0.0",
"react-native-svg": "^15.2.0",
"posthog-react-native-session-replay": "^0.1"
Copy link
Author

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

@marandaneto marandaneto changed the base branch from main to feat/surveys February 11, 2025 12:54
@marandaneto
Copy link
Member

@ian-craig merging it to the remote feat/surveys branch so I can merge my things within that branch too.
we can make PRs on top of that branch, thanks.

@marandaneto marandaneto marked this pull request as ready for review February 11, 2025 12:56
@marandaneto marandaneto merged commit de91db7 into PostHog:feat/surveys Feb 11, 2025
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

This PR adds comprehensive survey support to posthog-react-native by porting functionality from posthog-js, introducing a new survey provider component and related hooks for managing survey display and interactions.

  • Added posthog-core/src/posthog-surveys-types.ts with core survey type definitions to fix TypeScript module generation issues
  • Added PostHogSurveyProvider component in /posthog-react-native/src/surveys/PostHogSurveyProvider.tsx for survey state management and display
  • Added survey components in /posthog-react-native/src/surveys/components/ for rendering different question types and survey UI
  • Added survey storage and activation hooks in /posthog-react-native/src/surveys/ for managing survey state and persistence
  • Potential issue: The start_date check in getActiveMatchingSurveys.ts appears incorrect as it returns false when start_date exists

💡 (1/5) You can manually trigger the bot by mentioning @greptileai in a comment!

22 file(s) reviewed, 47 comment(s)
Edit PR Review Bot Settings | Greptile


interface ResponseBasedBranching {
type: SurveyQuestionBranchingType.ResponseBased
responseValues: Record<string, any>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: responseValues type is too permissive with Record<string, any>. Consider using a more specific type to ensure type safety

Comment on lines +191 to +195
/** @default StringMatching.Exact */
text_matching?: ActionStepStringMatching | null
href?: string | null
/** @default ActionStepStringMatching.Exact */
href_matching?: ActionStepStringMatching | null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: text_matching and href_matching have default values in comments but these aren't enforced by types. Consider using default type parameters

Comment on lines +489 to +494
const response = await this.fetchWithRetry(url, fetchOptions)
.then((response) => response.json() as Promise<SurveyResponse>)
.catch((error) => {
this._events.emit('error', error)
return { surveys: [] }
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: error handling returns empty surveys array but doesn't retry failed requests like other API calls. Consider using fetchWithRetry with retry options for consistency.

try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
OptionalRNSafeArea = require('react-native-safe-area-context')
} catch (e) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: empty catch block silently ignores errors that could be useful for debugging

Comment on lines +79 to 82
"react-native-safe-area-context": {
"optional": true
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: react-native-svg is missing from peerDependenciesMeta but is listed as an optional peer dependency. Add it to maintain consistency

Suggested change
"react-native-safe-area-context": {
"optional": true
}
}
"react-native-safe-area-context": {
"optional": true
},
"react-native-svg": {
"optional": true
}
}

Comment on lines +35 to +37
survey.questions.forEach((question, idx) => {
question.originalQuestionIndex = idx
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Mutating the survey object directly by adding originalQuestionIndex could cause issues if the same survey is used multiple times. Consider creating a new array with the indices.

Comment on lines +18 to +31
posthogStorage.ready().then(() => {
const lastSeenSurveyDate = posthogStorage.getPersistedProperty(PostHogPersistedProperty.SurveyLastSeenDate)
if (typeof lastSeenSurveyDate === 'string') {
setLastSeenSurveyDate(new Date(lastSeenSurveyDate))
}

const serialisedSeenSurveys = posthogStorage.getPersistedProperty(PostHogPersistedProperty.SurveysSeen)
if (typeof serialisedSeenSurveys === 'string') {
const parsedSeenSurveys: unknown = JSON.parse(serialisedSeenSurveys)
if (Array.isArray(parsedSeenSurveys) && typeof parsedSeenSurveys[0] === 'string') {
setSeenSurveys(parsedSeenSurveys)
}
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: no error handling for failed storage operations or invalid JSON

Comment on lines +93 to +94
question.choices.push(openEndedChoice)
shuffledOptions.push(openEndedChoice)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Mutating question.choices directly here could cause side effects. The choices array should be cloned before modification.

Comment on lines +19 to +22
const lastSeenSurveyDate = posthogStorage.getPersistedProperty(PostHogPersistedProperty.SurveyLastSeenDate)
if (typeof lastSeenSurveyDate === 'string') {
setLastSeenSurveyDate(new Date(lastSeenSurveyDate))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: check if lastSeenSurveyDate is a valid date string before creating Date object

greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
'indianred ': '#cd5c5c',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: The key 'indianred ' has a trailing space which could cause lookup issues. Remove the space from the key.

Suggested change
'indianred ': '#cd5c5c',
'indianred': '#cd5c5c',

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants