From b346f505341826cd5213a5a4d1eb60e255f3fd39 Mon Sep 17 00:00:00 2001 From: Peter Morgenstern Date: Tue, 10 Oct 2023 14:08:52 +0200 Subject: [PATCH 1/4] Improve DevMenu loading So that the DevMenu and Storybook won't get bundled into the production build. --- App.tsx | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/App.tsx b/App.tsx index 784e21d..04e75c8 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,4 @@ -import React, { useState } from 'react'; -import { addItem } from 'react-native-dev-menu'; +import React, { useState, useEffect, useCallback } from 'react'; import { ApolloProvider, @@ -7,29 +6,31 @@ import { NavigationProvider, } from '@app/providers'; -import Storybook from './.storybook/Storybook'; - -/** - * Add item to DevMenu (Cmd+D) to toggle between the app and Storybook. - */ -const addDevMenuItemForStorybook = ( - setShowStorybook: React.Dispatch>, - showStorybook: boolean, -) => { - addItem('Toggle Storybook', () => setShowStorybook(!showStorybook)).catch( - error => - error instanceof Error && - console.error( - `DevMenu.addItem "Toggle Storybook" error: ${error.toString()}`, - ), - ); -}; - export const App = () => { const [showStorybook, setShowStorybook] = useState(false); + const toggleStorybook = useCallback( + () => setShowStorybook(previousState => !previousState), + [], + ); + + useEffect( + () => { + if (__DEV__) { + // Add item to DevMenu (Cmd+D) to toggle between the app and Storybook. + // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment + const DevMenu = require('react-native-dev-menu'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + DevMenu.addItem('Toggle Storybook', toggleStorybook); + } + }, + // We only want to run this hook once. + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); if (__DEV__) { - addDevMenuItemForStorybook(setShowStorybook, showStorybook); + // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const Storybook = require('./.storybook/Storybook').default; if (showStorybook) { return ; From a8d2bc489b6068b397c5f595bc3737b991abc194 Mon Sep 17 00:00:00 2001 From: Peter Morgenstern Date: Tue, 10 Oct 2023 14:14:15 +0200 Subject: [PATCH 2/4] Get rid of react-native-dev-menu It's actually not needed but is included in ReactNative --- App.tsx | 7 ++----- README.md | 2 +- ios/Podfile.lock | 8 -------- package.json | 1 - yarn.lock | 10 ---------- 5 files changed, 3 insertions(+), 25 deletions(-) diff --git a/App.tsx b/App.tsx index 04e75c8..d7cca71 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; +import { DevSettings } from 'react-native'; import { ApolloProvider, @@ -16,11 +17,7 @@ export const App = () => { useEffect( () => { if (__DEV__) { - // Add item to DevMenu (Cmd+D) to toggle between the app and Storybook. - // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment - const DevMenu = require('react-native-dev-menu'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - DevMenu.addItem('Toggle Storybook', toggleStorybook); + DevSettings.addMenuItem('Toggle Storybook', toggleStorybook); } }, // We only want to run this hook once. diff --git a/README.md b/README.md index 514eb00..e04187e 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,4 @@ Commands are defined in the [`Justfile`](Justfile) and can be listed with [`just ## Storybook -Storybook can be toggled via the [DevMenu](https://github.com/zoontek/react-native-dev-menu) (CMD+D). +Storybook can be toggled via the ReactNative developer menu (open them by shaking the device e.g. with CMD+CTRL+Z). diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d5c6f3e..692f549 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -506,10 +506,6 @@ PODS: - React-Core - RNDateTimePicker (7.6.0): - React-Core - - RNDevMenu (4.1.1): - - React-Core - - React-Core/DevSupport - - React-RCTNetwork - RNGestureHandler (2.13.1): - React-Core - RNReanimated (3.5.4): @@ -619,7 +615,6 @@ DEPENDENCIES: - ReactNativeUiLib (from `../node_modules/react-native-ui-lib`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" - - RNDevMenu (from `../node_modules/react-native-dev-menu`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) @@ -735,8 +730,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-async-storage/async-storage" RNDateTimePicker: :path: "../node_modules/@react-native-community/datetimepicker" - RNDevMenu: - :path: "../node_modules/react-native-dev-menu" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNReanimated: @@ -805,7 +798,6 @@ SPEC CHECKSUMS: ReactNativeUiLib: 511a5eb03809a0b27f6aefa2b4d6f7c2d6ae4053 RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb RNDateTimePicker: ccd988deb223cbb2e669e157ec576c2c6217128c - RNDevMenu: 72807568fe4188bd4c40ce32675d82434b43c45d RNGestureHandler: 38aa38413896620338948fbb5c90579a7b1c3fde RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87 RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa diff --git a/package.json b/package.json index e45a752..27b3ace 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "react-dom": "18.2.0", "react-native": "0.72.5", "react-native-app-auth": "^7.1.0", - "react-native-dev-menu": "^4.1.1", "react-native-dotenv": "^3.4.9", "react-native-encrypted-storage": "^4.0.3", "react-native-gesture-handler": "^2.13.1", diff --git a/yarn.lock b/yarn.lock index 5815b35..e403e51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7308,7 +7308,6 @@ __metadata: react-dom: 18.2.0 react-native: 0.72.5 react-native-app-auth: ^7.1.0 - react-native-dev-menu: ^4.1.1 react-native-dotenv: ^3.4.9 react-native-encrypted-storage: ^4.0.3 react-native-gesture-handler: ^2.13.1 @@ -17380,15 +17379,6 @@ __metadata: languageName: node linkType: hard -"react-native-dev-menu@npm:^4.1.1": - version: 4.1.1 - resolution: "react-native-dev-menu@npm:4.1.1" - peerDependencies: - react-native: ">=0.61.0" - checksum: 13b7b37cab629d71e49f684a343e858243c7b7e79fd85c8a08ae4886c4cf24ca086610baa4c3c4b4f4ecaf8834f1680ecabdac4921f207c47c1b4f1a9f7d44dd - languageName: node - linkType: hard - "react-native-dotenv@npm:^3.4.9": version: 3.4.9 resolution: "react-native-dotenv@npm:3.4.9" From 9280cf2d220db6756aef544718e957151069a2d1 Mon Sep 17 00:00:00 2001 From: Peter Morgenstern Date: Tue, 10 Oct 2023 14:32:07 +0200 Subject: [PATCH 3/4] Move Storybook into its own provider So that App.tsx is easier to grasp again. --- App.tsx | 42 ++++++--------------------- src/providers/StorybookProvider.tsx | 45 +++++++++++++++++++++++++++++ src/providers/index.ts | 1 + 3 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 src/providers/StorybookProvider.tsx diff --git a/App.tsx b/App.tsx index d7cca71..40c5894 100644 --- a/App.tsx +++ b/App.tsx @@ -1,45 +1,21 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { DevSettings } from 'react-native'; +import React from 'react'; import { ApolloProvider, AuthProvider, NavigationProvider, + StorybookProvider, } from '@app/providers'; export const App = () => { - const [showStorybook, setShowStorybook] = useState(false); - const toggleStorybook = useCallback( - () => setShowStorybook(previousState => !previousState), - [], - ); - - useEffect( - () => { - if (__DEV__) { - DevSettings.addMenuItem('Toggle Storybook', toggleStorybook); - } - }, - // We only want to run this hook once. - // eslint-disable-next-line react-hooks/exhaustive-deps - [], - ); - - if (__DEV__) { - // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - const Storybook = require('./.storybook/Storybook').default; - - if (showStorybook) { - return ; - } - } - return ( - - - - - + + + + + + + ); }; diff --git a/src/providers/StorybookProvider.tsx b/src/providers/StorybookProvider.tsx new file mode 100644 index 0000000..36bd79c --- /dev/null +++ b/src/providers/StorybookProvider.tsx @@ -0,0 +1,45 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { DevSettings } from 'react-native'; + +export type StorybookProviderType = React.FC<{ children: React.ReactNode }>; + +/** + * This provider adds a "Toggle Storybook" item to the ReactNative developer menu but only + * if the app is running in development mode. + * + * The ReactNative developer menu can be opened by shaking the device e.g. on Mac in the iOS + * simulator press Cmd+Ctrl+Z. + */ +export const StorybookProvider: StorybookProviderType = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [showStorybook, setShowStorybook] = useState(false); + const toggleStorybook = useCallback( + () => setShowStorybook(previousState => !previousState), + [], + ); + + useEffect( + () => { + if (__DEV__) { + DevSettings.addMenuItem('Toggle Storybook', toggleStorybook); + } + }, + // We only want to run this hook once. + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + if (__DEV__) { + // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const Storybook = require('../../.storybook/Storybook').default; + + if (showStorybook) { + return ; + } + } + + return children; +}; diff --git a/src/providers/index.ts b/src/providers/index.ts index 3d2dc0a..7f9e2e5 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,3 +1,4 @@ export * from './ApolloProvider'; export * from './AuthProvider'; export * from './NavigationProvider'; +export * from './StorybookProvider'; From be8562aa9274f0749a0f441f64bb36587831e414 Mon Sep 17 00:00:00 2001 From: Peter Morgenstern Date: Tue, 10 Oct 2023 15:07:41 +0200 Subject: [PATCH 4/4] Adjust mocks for StorybookProvider --- .../Libraries/Utilities/DevSettings.ts | 5 +++++ __tests__/App.test.tsx | 4 ++-- src/providers/StorybookProvider.tsx | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 __mocks__/react-native/Libraries/Utilities/DevSettings.ts diff --git a/__mocks__/react-native/Libraries/Utilities/DevSettings.ts b/__mocks__/react-native/Libraries/Utilities/DevSettings.ts new file mode 100644 index 0000000..ce6b656 --- /dev/null +++ b/__mocks__/react-native/Libraries/Utilities/DevSettings.ts @@ -0,0 +1,5 @@ +const DevSettings = { + addMenuItem: jest.fn(), +}; + +export default DevSettings; diff --git a/__tests__/App.test.tsx b/__tests__/App.test.tsx index bf1e636..ba33a58 100644 --- a/__tests__/App.test.tsx +++ b/__tests__/App.test.tsx @@ -3,8 +3,8 @@ import { render } from '@testing-library/react-native'; import { App } from '../App'; -jest.mock('react-native-dev-menu', () => ({ - addItem: jest.fn(() => Promise.resolve()), +jest.mock('react-native/Libraries/Utilities/DevSettings', () => ({ + addMenuItem: jest.fn(), })); jest.mock('@app/hooks/useAuth', () => ({ diff --git a/src/providers/StorybookProvider.tsx b/src/providers/StorybookProvider.tsx index 36bd79c..e69186e 100644 --- a/src/providers/StorybookProvider.tsx +++ b/src/providers/StorybookProvider.tsx @@ -1,5 +1,11 @@ +/* istanbul ignore file */ + import React, { useState, useEffect, useCallback } from 'react'; -import { DevSettings } from 'react-native'; +// We need to import directly from the file instead of from react-native +// because we need to mock this in the tests. +// @ts-expect-error It's falsely reported that DevSettings has no exported member addMenuItem, +// but there actually is: https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Utilities/DevSettings.d.ts +import { addMenuItem } from 'react-native/Libraries/Utilities/DevSettings'; export type StorybookProviderType = React.FC<{ children: React.ReactNode }>; @@ -9,6 +15,9 @@ export type StorybookProviderType = React.FC<{ children: React.ReactNode }>; * * The ReactNative developer menu can be opened by shaking the device e.g. on Mac in the iOS * simulator press Cmd+Ctrl+Z. + * + * NOTE: If __DEV__ is false, nothing related to Storybook is rendered and this provider does + * basically nothing. */ export const StorybookProvider: StorybookProviderType = ({ children, @@ -24,7 +33,8 @@ export const StorybookProvider: StorybookProviderType = ({ useEffect( () => { if (__DEV__) { - DevSettings.addMenuItem('Toggle Storybook', toggleStorybook); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + addMenuItem('Toggle Storybook', toggleStorybook); } }, // We only want to run this hook once.