From 9388f1c2813215f38c681ff0ebab702b150a4eda Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 27 Mar 2024 13:04:14 +0000 Subject: [PATCH] [PLAT-11855] Forward navigation refs (#431) fix: :bug: Fix an issue where refs are not forwarded to the NavigationContainer --- CHANGELOG.md | 4 ++ package-lock.json | 7 ++- .../lib/create-navigation-container.tsx | 58 +++++++++---------- .../test/create-navigation-container.test.tsx | 28 +++++++-- 4 files changed, 61 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048c104cd..bc79da6fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index 5f12e76f0..ac0c68aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24788,16 +24788,17 @@ "react-native-navigation": "7.37.2" }, "peerDependencies": { - "@bugsnag/core-performance": "^2.2.0", - "@bugsnag/react-native-performance": "^2.2.0", + "@bugsnag/core-performance": "^2.3.0-alpha.0", + "@bugsnag/react-native-performance": "^2.3.0", "react-native-navigation": "*" } }, "packages/plugin-react-navigation": { + "name": "@bugsnag/plugin-react-navigation-performance", "version": "2.3.0", "license": "MIT", "devDependencies": { - "@bugsnag/core-performance": "^2.2.0", + "@bugsnag/core-performance": "^2.3.0-alpha.0", "@bugsnag/react-native-performance": "^2.3.0", "@react-native/babel-preset": "^0.74.0", "@react-navigation/native": "^6.1.9", diff --git a/packages/plugin-react-navigation/lib/create-navigation-container.tsx b/packages/plugin-react-navigation/lib/create-navigation-container.tsx index a0ee26e42..368e0a3e0 100644 --- a/packages/plugin-react-navigation/lib/create-navigation-container.tsx +++ b/packages/plugin-react-navigation/lib/create-navigation-container.tsx @@ -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) => - forwardRef( - (props, _ref) => { - const { onStateChange, ...rest } = props +type CreateNavigationContainer = (NavigationContainerComponent: typeof NavigationContainer, spanFactory: SpanFactory) => typeof NavigationContainer +type NavigationContainerRef = NavigationContainerRefWithCurrent - const navigationContainerRef = useNavigationContainerRef() - const [routeName, setRouteName] = React.useState() - const routeNameRef = useRef() +export const createNavigationContainer: CreateNavigationContainer = (NavigationContainerComponent = NavigationContainer, spanFactory: SpanFactory) => { + return forwardRef((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() + const routeNameRef = useRef() - 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 ( - - - - ) + if (typeof onStateChange === 'function') { + onStateChange.apply(this, args) + } } - ) + + return ( + + + + ) + }) as typeof NavigationContainerComponent +} diff --git a/packages/plugin-react-navigation/test/create-navigation-container.test.tsx b/packages/plugin-react-navigation/test/create-navigation-container.test.tsx index 7f8514891..d97dc941e 100644 --- a/packages/plugin-react-navigation/test/create-navigation-container.test.tsx +++ b/packages/plugin-react-navigation/test/create-navigation-container.test.tsx @@ -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' @@ -17,20 +18,39 @@ describe('createNavigationContainer', () => { ) - 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() + const BugsnagNavigationContainer = createNavigationContainer(NavigationContainer, spanFactory) + + render( + + + + ) + + 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 {