Skip to content

Commit

Permalink
[PLAT-11855] Forward navigation refs (#431)
Browse files Browse the repository at this point in the history
fix: 🐛 Fix an issue where refs are not forwarded to the NavigationContainer
  • Loading branch information
gingerbenw committed Mar 27, 2024
1 parent 75a38e9 commit 9388f1c
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -16,6 +16,10 @@
- Rename package import from `@bugsnag/react-native-navigation-performance` to `@bugsnag/plugin-react-native-navigation-performance`
- Rename plugin type from `ReactNativeNavigationPlugin` to `BugsnagPluginReactNativeNavigationPerformance`

### Fixed

- (plugin-react-navigation) Fix an issue where refs are not forwarded to the NavigationContainer [#431](https://github.com/bugsnag/bugsnag-js-performance/pull/431)

## v2.3.0 (2024-03-20)

This release adds support for instrumenting navigation spans when using the [react-native-navigation](https://github.com/wix/react-native-navigation) library
Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -1,44 +1,44 @@
import { type SpanFactory } from '@bugsnag/core-performance'
import { type ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import { NavigationContainer, useNavigationContainerRef, type NavigationContainerProps } from '@react-navigation/native'
import { NavigationContainer, useNavigationContainerRef, type NavigationContainerProps, type NavigationContainerRefWithCurrent } from '@react-navigation/native'
import React, { forwardRef, useRef } from 'react'
import { NavigationContextProvider } from './navigation-context'

// Prevent rollup plugin from tree shaking NavigationContextProvider
const Provider = NavigationContextProvider

export const createNavigationContainer = (Container = NavigationContainer, spanFactory: SpanFactory<ReactNativeConfiguration>) =>
forwardRef<typeof Container, NavigationContainerProps>(
(props, _ref) => {
const { onStateChange, ...rest } = props
type CreateNavigationContainer = (NavigationContainerComponent: typeof NavigationContainer, spanFactory: SpanFactory<ReactNativeConfiguration>) => typeof NavigationContainer
type NavigationContainerRef = NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>

const navigationContainerRef = useNavigationContainerRef()
const [routeName, setRouteName] = React.useState<string>()
const routeNameRef = useRef<string>()
export const createNavigationContainer: CreateNavigationContainer = (NavigationContainerComponent = NavigationContainer, spanFactory: SpanFactory<ReactNativeConfiguration>) => {
return forwardRef<NavigationContainerRef, NavigationContainerProps>((props, _ref) => {
const { onStateChange, ...rest } = props

const wrappedOnStateChange: typeof onStateChange = (...args) => {
const currentRoute = navigationContainerRef.current
? navigationContainerRef.current.getCurrentRoute()
: null
const navigationContainerRef = _ref as NavigationContainerRef || useNavigationContainerRef()
const [routeName, setRouteName] = React.useState<string>()
const routeNameRef = useRef<string>()

if (currentRoute) {
routeNameRef.current = currentRoute.name
setRouteName(currentRoute.name)
}
const wrappedOnStateChange: typeof onStateChange = (...args) => {
const currentRoute = navigationContainerRef ? navigationContainerRef.getCurrentRoute() : null

if (typeof onStateChange === 'function') {
onStateChange.apply(this, args)
}
if (currentRoute) {
routeNameRef.current = currentRoute.name
setRouteName(currentRoute.name)
}

return (
<Provider spanFactory={spanFactory} currentRoute={routeName}>
<Container
{...rest}
onStateChange={wrappedOnStateChange}
ref={navigationContainerRef}
/>
</Provider>
)
if (typeof onStateChange === 'function') {
onStateChange.apply(this, args)
}
}
)

return (
<Provider spanFactory={spanFactory} currentRoute={routeName}>
<NavigationContainerComponent
{...rest}
onStateChange={wrappedOnStateChange}
ref={navigationContainerRef}
/>
</Provider>
)
}) as typeof NavigationContainerComponent
}
@@ -1,9 +1,10 @@
import { MockSpanFactory } from '@bugsnag/js-performance-test-utilities'
import { type ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import { NavigationContainer, type ParamListBase } from '@react-navigation/native'
import { NavigationContainer, createNavigationContainerRef, type ParamListBase } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import '@testing-library/jest-native/extend-expect'
import { fireEvent, render, screen } from '@testing-library/react-native'
import { act, fireEvent, render, screen } from '@testing-library/react-native'
import React from 'react'
import { Button, Text, View } from 'react-native'
import { createNavigationContainer } from '../lib/create-navigation-container'

Expand All @@ -17,20 +18,39 @@ describe('createNavigationContainer', () => {
<RootNavigator />
</BugsnagNavigationContainer>
)
fireEvent.press(screen.getByText('Go to route 2'))

fireEvent.press(screen.getByText('Go to route 2'))
expect(screen.getByText('Route 2')).toBeOnTheScreen()

expect(spanFactory.startSpan).toHaveBeenCalledTimes(1)
expect(spanFactory.startSpan).toHaveBeenCalledWith('[Navigation]Route 2', { isFirstClass: true })

fireEvent.press(screen.getByText('Go back'))

expect(screen.getByText('Route 1')).toBeOnTheScreen()

expect(spanFactory.startSpan).toHaveBeenCalledTimes(2)
expect(spanFactory.startSpan).toHaveBeenCalledWith('[Navigation]Route 1', { isFirstClass: true })
})

it('forwards the provided ref to the NavigationContainer', () => {
const navigationRef = createNavigationContainerRef()
const spanFactory = new MockSpanFactory<ReactNativeConfiguration>()
const BugsnagNavigationContainer = createNavigationContainer(NavigationContainer, spanFactory)

render(
<BugsnagNavigationContainer ref={navigationRef}>
<RootNavigator />
</BugsnagNavigationContainer>
)

act(() => {
// @ts-expect-error navigate method expects type 'never'
navigationRef.navigate('Route 2')
})

expect(spanFactory.startSpan).toHaveBeenCalledTimes(1)
expect(spanFactory.startSpan).toHaveBeenCalledWith('[Navigation]Route 2', { isFirstClass: true })
})
})

interface RootStackParamList extends ParamListBase {
Expand Down

0 comments on commit 9388f1c

Please sign in to comment.