From d814e42d2d7a27b43ecb18ad9815deb527e5e9f7 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 8 Sep 2026 13:00:36 -0500 Subject: [PATCH 1/4] perf(hooks): deep-import context and keep core clients off narrow graphs Stack 2/3 of #382. Hooks-only changes: deep imports of YouVersionContext, client-override fallback shape per hook, and query-key changes. --- packages/hooks/package.json | 10 +++-- .../hooks/src/context/YouVersionProvider.tsx | 43 ++++++++++++------- packages/hooks/src/internal/useApiClient.ts | 2 +- .../hooks/src/internal/useQueryKeyBase.ts | 2 +- .../hooks/src/internal/versionFilterKey.ts | 11 ++--- packages/hooks/src/useApiData.ssr.test.tsx | 4 +- packages/hooks/src/useBibleClient.ts | 2 +- packages/hooks/src/useBook.ts | 12 ++++-- packages/hooks/src/useBooks.ts | 11 +++-- packages/hooks/src/useChapter.test.tsx | 17 +++++++- packages/hooks/src/useChapter.ts | 14 ++++-- packages/hooks/src/useChapters.ts | 14 ++++-- packages/hooks/src/useHighlightAuthActions.ts | 2 +- packages/hooks/src/useLanguage.test.tsx | 18 +++++++- packages/hooks/src/useLanguage.ts | 14 ++++-- packages/hooks/src/useLanguageClient.ts | 2 +- packages/hooks/src/useLanguages.ts | 15 ++++--- packages/hooks/src/useOrganizationsClient.ts | 2 +- packages/hooks/src/usePassage.ts | 28 ++++++++++-- packages/hooks/src/useTheme.ts | 2 +- packages/hooks/src/useVOTD.ts | 11 +++-- packages/hooks/src/useVerse.ts | 14 ++++-- packages/hooks/src/useVerses.ts | 14 ++++-- packages/hooks/src/useVersion.ts | 11 +++-- packages/hooks/src/useVersions.ts | 14 ++++-- packages/hooks/tsup.config.ts | 16 +++++++ 26 files changed, 221 insertions(+), 84 deletions(-) create mode 100644 packages/hooks/tsup.config.ts diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 82f91645..5715e277 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -11,7 +11,9 @@ "CHANGELOG.md", "!dist/**/*.test.*", "!dist/__tests__", - "!dist/test" + "!dist/test", + "!dist/**/*.d.ts.map", + "!dist/**/*.d.cts" ], "publishConfig": { "access": "public", @@ -38,10 +40,10 @@ } }, "scripts": { - "dev": "tsup src/index.ts src/test-utils.tsx --format cjs,esm --external jsdom --watch", - "build:js": "tsup src/index.ts src/test-utils.tsx --format cjs,esm --external jsdom", + "dev": "tsup --watch", + "build:js": "tsup", "build:types": "tsc -p tsconfig.build.json", - "build": "pnpm build:js && pnpm build:types", + "build": "rm -rf dist && pnpm build:js && pnpm build:types", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/hooks/src/context/YouVersionProvider.tsx b/packages/hooks/src/context/YouVersionProvider.tsx index 81a0035b..d8ab596f 100644 --- a/packages/hooks/src/context/YouVersionProvider.tsx +++ b/packages/hooks/src/context/YouVersionProvider.tsx @@ -19,16 +19,12 @@ interface YouVersionProviderPropsBase { theme?: 'light' | 'dark' | 'system'; /** * Integrator display name for the sign-in dialog body copy. Synced onto - * `YouVersionPlatformConfiguration.appName`. The UI package also mirrors this - * onto its bundled core copy (tsup `noExternal`), so pass it via - * `YouVersionProvider` props — do not set the config from a separate - * `@youversion/platform-core` import when consuming `@youversion/platform-react-ui`. + * `YouVersionPlatformConfiguration.appName`. */ appName?: string; /** * Optional pitch line for the sign-in dialog. Synced onto - * `YouVersionPlatformConfiguration.signInPromptMessage` (and mirrored by the - * UI provider onto its bundled core copy — same dual-instance caveat as `appName`). + * `YouVersionPlatformConfiguration.signInPromptMessage`. */ signInPromptMessage?: string; /** @@ -72,7 +68,26 @@ interface YouVersionProviderPropsWithoutAuth extends YouVersionProviderPropsBase authRedirectUrl?: never; } -const AuthProvider = lazy(() => import('./YouVersionAuthProvider')); +const loadAuthProvider = () => import('./YouVersionAuthProvider'); + +function YouVersionAuthGate({ + config, + userInfo, + children, +}: { + config: { appKey: string; apiHost?: string; redirectUri: string }; + userInfo?: YouVersionUserInfoJSON | null; + children: ReactNode; +}): React.ReactElement { + const AuthProvider = useMemo(() => lazy(loadAuthProvider), []); + return ( + + + {children} + + + ); +} function useResolvedTheme(theme: 'light' | 'dark' | 'system'): 'light' | 'dark' { const [resolved, setResolved] = useState<'light' | 'dark'>(() => { @@ -199,14 +214,12 @@ function YouVersionProviderInner( return ( - - - {children} - - + + {children} + ); diff --git a/packages/hooks/src/internal/useApiClient.ts b/packages/hooks/src/internal/useApiClient.ts index cebcca3f..e5a25675 100644 --- a/packages/hooks/src/internal/useApiClient.ts +++ b/packages/hooks/src/internal/useApiClient.ts @@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'; import { ApiClient } from '@youversion/platform-core'; -import { YouVersionContext } from '../context'; +import { YouVersionContext } from '../context/YouVersionContext'; /** * @internal diff --git a/packages/hooks/src/internal/useQueryKeyBase.ts b/packages/hooks/src/internal/useQueryKeyBase.ts index 51492bf9..18a9915c 100644 --- a/packages/hooks/src/internal/useQueryKeyBase.ts +++ b/packages/hooks/src/internal/useQueryKeyBase.ts @@ -1,7 +1,7 @@ 'use client'; import { useContext, useMemo } from 'react'; -import { YouVersionContext } from '../context'; +import { YouVersionContext } from '../context/YouVersionContext'; import { serializeAdditionalHeaders } from './additionalHeadersKey'; import { serializeVersionFilters } from './versionFilterKey'; diff --git a/packages/hooks/src/internal/versionFilterKey.ts b/packages/hooks/src/internal/versionFilterKey.ts index d5586fcf..ac0c23f4 100644 --- a/packages/hooks/src/internal/versionFilterKey.ts +++ b/packages/hooks/src/internal/versionFilterKey.ts @@ -1,12 +1,13 @@ -import { YouVersionPlatformConfiguration } from '@youversion/platform-core'; +import { getVersionFilterSnapshot } from '@youversion/platform-core'; /** * @internal * Converts the active Bible version filter to stable text. * - * The filter lives on `YouVersionPlatformConfiguration`, which `YouVersionProvider` - * writes from its `permittedVersionIds`, `excludedVersionIds`, and - * `permittedLanguageTags` props during render, before children read this value. + * `YouVersionProvider` writes those lists onto + * `YouVersionPlatformConfiguration` during render, before children read this + * value. Readers use `getVersionFilterSnapshot` so unused auth storage stays + * off the `useChapter` graph. * * The ids and tags are sorted before serialization, so two lists with the same * entries produce the same text. An unset list serializes as `null`, which keeps @@ -18,7 +19,7 @@ import { YouVersionPlatformConfiguration } from '@youversion/platform-core'; */ export function serializeVersionFilters(): string | null { const { permittedVersionIds, excludedVersionIds, permittedLanguageTags } = - YouVersionPlatformConfiguration; + getVersionFilterSnapshot(); if ( permittedVersionIds === undefined && diff --git a/packages/hooks/src/useApiData.ssr.test.tsx b/packages/hooks/src/useApiData.ssr.test.tsx index fc0d74b9..ca10ecbb 100644 --- a/packages/hooks/src/useApiData.ssr.test.tsx +++ b/packages/hooks/src/useApiData.ssr.test.tsx @@ -7,7 +7,7 @@ */ import { renderToString } from 'react-dom/server'; import { describe, expect, it, vi } from 'vitest'; -import { BibleClient } from '@youversion/platform-core'; +import * as core from '@youversion/platform-core'; import { YouVersionProvider } from './context/YouVersionProvider'; import { useChapter } from './useChapter'; @@ -20,7 +20,7 @@ function Chapter() { describe('useApiData — server-side rendering', () => { it('renders the loading state without crashing or fetching', () => { const getChapter = vi - .spyOn(BibleClient.prototype, 'getChapter') + .spyOn(core, 'getChapter') .mockRejectedValue(new Error('must not be called during SSR')); const html = renderToString( diff --git a/packages/hooks/src/useBibleClient.ts b/packages/hooks/src/useBibleClient.ts index b51b8afb..11e2d3fb 100644 --- a/packages/hooks/src/useBibleClient.ts +++ b/packages/hooks/src/useBibleClient.ts @@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'; import { BibleClient } from '@youversion/platform-core'; -import { YouVersionContext } from './context'; +import { YouVersionContext } from './context/YouVersionContext'; import { useApiClient } from './internal/useApiClient'; export function useBibleClient(): BibleClient { diff --git a/packages/hooks/src/useBook.ts b/packages/hooks/src/useBook.ts index 4d76b5f1..215cc47a 100644 --- a/packages/hooks/src/useBook.ts +++ b/packages/hooks/src/useBook.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getBook, type BibleBook } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleBook } from '@youversion/platform-core'; export type UseBookResult = UseNamedQueryResult<'book', BibleBook>; @@ -13,12 +15,14 @@ export function useBook( book: string, options?: UseApiDataOptions, ): UseBookResult { - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { data, loading, error, refetch } = useApiData( [...keyBase, 'book', versionId, book], - () => bibleClient.getBook(versionId, book), + () => + bibleClient ? bibleClient.getBook(versionId, book) : getBook(apiClient, versionId, book), { enabled: options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useBooks.ts b/packages/hooks/src/useBooks.ts index fdb6b3e1..0c22662d 100644 --- a/packages/hooks/src/useBooks.ts +++ b/packages/hooks/src/useBooks.ts @@ -1,16 +1,19 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getBooks, type BibleBook, type Collection } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleBook, Collection } from '@youversion/platform-core'; import { useHookOverride } from './useHookOverride'; export type UseBooksResult = UseNamedQueryResult<'books', Collection>; export function useBooks(versionId: number, options?: UseApiDataOptions): UseBooksResult { const override = useHookOverride('useBooks'); - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { @@ -20,7 +23,7 @@ export function useBooks(versionId: number, options?: UseApiDataOptions): UseBoo refetch, } = useApiData>( [...keyBase, 'books', versionId], - () => bibleClient.getBooks(versionId), + () => (bibleClient ? bibleClient.getBooks(versionId) : getBooks(apiClient, versionId)), { enabled: !override && options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useChapter.test.tsx b/packages/hooks/src/useChapter.test.tsx index 2b23ea02..e84f0604 100644 --- a/packages/hooks/src/useChapter.test.tsx +++ b/packages/hooks/src/useChapter.test.tsx @@ -2,7 +2,8 @@ import { render, renderHook, waitFor, act } from '@testing-library/react'; import { describe, expect, vi, beforeEach, it } from 'vitest'; import { useChapter, type UseChapterResult } from './useChapter'; import { YouVersionContext } from './context'; -import { type BibleChapter } from '@youversion/platform-core'; +import * as core from '@youversion/platform-core'; +import type { BibleChapter } from '@youversion/platform-core'; import { createBibleClientStub, createYVWrapper, TestQueryClientProvider } from './test/utils'; describe('useChapter', () => { @@ -35,6 +36,20 @@ describe('useChapter', () => { expect.soft(result.current.chapter).toEqual(mockChapter); }); + it('fetches via getChapter when the provider has no bibleClient override', async () => { + const spy = vi.spyOn(core, 'getChapter').mockResolvedValue(mockChapter); + const bare = createYVWrapper('test-app-key'); + const { result } = renderHook(() => useChapter(111, 'MAT', 1), { wrapper: bare }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(spy).toHaveBeenCalled(); + expect(result.current.chapter).toEqual(mockChapter); + spy.mockRestore(); + }); + it.each([ { param: 'versionId', diff --git a/packages/hooks/src/useChapter.ts b/packages/hooks/src/useChapter.ts index deee93e3..239fc14c 100644 --- a/packages/hooks/src/useChapter.ts +++ b/packages/hooks/src/useChapter.ts @@ -1,9 +1,11 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getChapter, type BibleChapter } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleChapter } from '@youversion/platform-core'; export type UseChapterResult = UseNamedQueryResult<'chapter', BibleChapter>; @@ -13,7 +15,8 @@ export function useChapter( chapter: number, options?: UseApiDataOptions, ): UseChapterResult { - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { @@ -23,7 +26,10 @@ export function useChapter( refetch, } = useApiData( [...keyBase, 'chapter', versionId, book, chapter], - () => bibleClient.getChapter(versionId, book, chapter), + () => + bibleClient + ? bibleClient.getChapter(versionId, book, chapter) + : getChapter(apiClient, versionId, book, chapter), { enabled: options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useChapters.ts b/packages/hooks/src/useChapters.ts index 23cc2895..c10b817d 100644 --- a/packages/hooks/src/useChapters.ts +++ b/packages/hooks/src/useChapters.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getChapters, type BibleChapter, type Collection } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleChapter, Collection } from '@youversion/platform-core'; export type UseChaptersResult = UseNamedQueryResult<'chapters', Collection>; @@ -13,7 +15,8 @@ export function useChapters( book: string, options?: UseApiDataOptions, ): UseChaptersResult { - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); // Don't attempt to fetch if book is invalid @@ -26,7 +29,10 @@ export function useChapters( refetch, } = useApiData>( [...keyBase, 'chapters', versionId, book], - () => bibleClient.getChapters(versionId, book), + () => + bibleClient + ? bibleClient.getChapters(versionId, book) + : getChapters(apiClient, versionId, book), { enabled: options?.enabled !== false && isValidBook, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useHighlightAuthActions.ts b/packages/hooks/src/useHighlightAuthActions.ts index 8eba27ef..2e675ed2 100644 --- a/packages/hooks/src/useHighlightAuthActions.ts +++ b/packages/hooks/src/useHighlightAuthActions.ts @@ -10,7 +10,7 @@ import { YouVersionPlatformConfiguration, type DataExchangeCallbackResult, } from '@youversion/platform-core'; -import { YouVersionContext } from './context'; +import { YouVersionContext } from './context/YouVersionContext'; import { YouVersionAuthContext } from './context/YouVersionAuthContext'; import { useApiClient } from './internal/useApiClient'; diff --git a/packages/hooks/src/useLanguage.test.tsx b/packages/hooks/src/useLanguage.test.tsx index 0882b918..c53c8538 100644 --- a/packages/hooks/src/useLanguage.test.tsx +++ b/packages/hooks/src/useLanguage.test.tsx @@ -1,6 +1,8 @@ import { renderHook, waitFor, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { useLanguage } from './useLanguage'; +import * as core from '@youversion/platform-core'; +import type { Language } from '@youversion/platform-core'; import { createLanguagesClientStub, createYVWrapper } from './test/utils'; describe('useLanguage', () => { @@ -8,7 +10,7 @@ describe('useLanguage', () => { const languagesClient = createLanguagesClientStub({ getLanguage: mockGetLanguage }); const wrapper = createYVWrapper('test-app-key', { languagesClient }); - const mockLanguage = { + const mockLanguage: Language = { id: 'en', language: 'en', script: 'Latn', @@ -43,6 +45,20 @@ describe('useLanguage', () => { expect.soft(result.current.language).toEqual(mockLanguage); }); + it('fetches via getLanguage when the provider has no languagesClient override', async () => { + const spy = vi.spyOn(core, 'getLanguage').mockResolvedValue(mockLanguage); + const bare = createYVWrapper('test-app-key'); + const { result } = renderHook(() => useLanguage('en'), { wrapper: bare }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(spy).toHaveBeenCalled(); + expect(result.current.language).toEqual(mockLanguage); + spy.mockRestore(); + }); + it('should refetch when languageId changes', async () => { const { result, rerender } = renderHook(({ languageId }) => useLanguage(languageId), { wrapper, diff --git a/packages/hooks/src/useLanguage.ts b/packages/hooks/src/useLanguage.ts index 8dd8bb1c..2328c294 100644 --- a/packages/hooks/src/useLanguage.ts +++ b/packages/hooks/src/useLanguage.ts @@ -1,9 +1,11 @@ 'use client'; +import { useContext } from 'react'; +import { getLanguage, type Language } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; -import { type Language } from '@youversion/platform-core'; -import { useLanguagesClient } from './useLanguageClient'; import type { UseNamedQueryResult } from './useQueryResult'; import { useHookOverride } from './useHookOverride'; @@ -11,12 +13,16 @@ export type UseLanguageResult = UseNamedQueryResult<'language', Language>; export function useLanguage(languageId: string, apiOptions?: UseApiDataOptions): UseLanguageResult { const override = useHookOverride('useLanguage'); - const languagesClient = useLanguagesClient(); + const languagesClient = useContext(YouVersionContext)?.languagesClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { data, loading, error, refetch } = useApiData( [...keyBase, 'language', languageId], - () => languagesClient.getLanguage(languageId), + () => + languagesClient + ? languagesClient.getLanguage(languageId) + : getLanguage(apiClient, languageId), { enabled: !override && apiOptions?.enabled !== false, keepPreviousData: apiOptions?.keepPreviousData, diff --git a/packages/hooks/src/useLanguageClient.ts b/packages/hooks/src/useLanguageClient.ts index 1dc7b929..11c029cb 100644 --- a/packages/hooks/src/useLanguageClient.ts +++ b/packages/hooks/src/useLanguageClient.ts @@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'; import { LanguagesClient } from '@youversion/platform-core'; -import { YouVersionContext } from './context'; +import { YouVersionContext } from './context/YouVersionContext'; import { useApiClient } from './internal/useApiClient'; export function useLanguagesClient(): LanguagesClient { diff --git a/packages/hooks/src/useLanguages.ts b/packages/hooks/src/useLanguages.ts index 98a1883b..d8b4b1bc 100644 --- a/packages/hooks/src/useLanguages.ts +++ b/packages/hooks/src/useLanguages.ts @@ -1,13 +1,16 @@ 'use client'; -import { useApiData, type UseApiDataOptions } from './useApiData'; -import { useQueryKeyBase } from './internal/useQueryKeyBase'; +import { useContext } from 'react'; import { + getLanguages, type GetLanguagesOptions, type Collection, type Language, } from '@youversion/platform-core'; -import { useLanguagesClient } from './useLanguageClient'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; +import { useApiData, type UseApiDataOptions } from './useApiData'; +import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; import { useHookOverride } from './useHookOverride'; @@ -18,7 +21,8 @@ export function useLanguages( apiOptions?: UseApiDataOptions, ): UseLanguagesResult { const override = useHookOverride('useLanguages'); - const languagesClient = useLanguagesClient(); + const languagesClient = useContext(YouVersionContext)?.languagesClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { data, loading, error, refetch } = useApiData>( @@ -30,7 +34,8 @@ export function useLanguages( options?.page_size, options?.page_token, ], - () => languagesClient.getLanguages(options), + () => + languagesClient ? languagesClient.getLanguages(options) : getLanguages(apiClient, options), { enabled: !override && apiOptions?.enabled !== false, keepPreviousData: apiOptions?.keepPreviousData, diff --git a/packages/hooks/src/useOrganizationsClient.ts b/packages/hooks/src/useOrganizationsClient.ts index 08f451d0..1002913c 100644 --- a/packages/hooks/src/useOrganizationsClient.ts +++ b/packages/hooks/src/useOrganizationsClient.ts @@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'; import { OrganizationsClient } from '@youversion/platform-core'; -import { YouVersionContext } from './context'; +import { YouVersionContext } from './context/YouVersionContext'; import { useApiClient } from './internal/useApiClient'; export function useOrganizationsClient(): OrganizationsClient { diff --git a/packages/hooks/src/usePassage.ts b/packages/hooks/src/usePassage.ts index 6d7c2b7a..496e7ad3 100644 --- a/packages/hooks/src/usePassage.ts +++ b/packages/hooks/src/usePassage.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getPassage, type BiblePassage } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BiblePassage } from '@youversion/platform-core'; import { useHookOverride } from './useHookOverride'; export type UsePassageProps = { @@ -34,7 +36,8 @@ export function usePassage({ options, }: UsePassageProps): UsePassageResult { const override = useHookOverride('usePassage'); - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); // Don't attempt to fetch if usfm is invalid @@ -43,7 +46,24 @@ export function usePassage({ const { data, loading, error, refetch } = useApiData( [...keyBase, 'passage', versionId, usfm, format, include_headings, include_notes, transform], () => - bibleClient.getPassage(versionId, usfm, format, include_headings, include_notes, transform), + bibleClient + ? bibleClient.getPassage( + versionId, + usfm, + format, + include_headings, + include_notes, + transform, + ) + : getPassage( + apiClient, + versionId, + usfm, + format, + include_headings, + include_notes, + transform, + ), { enabled: !override && options?.enabled !== false && isValidUsfm, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useTheme.ts b/packages/hooks/src/useTheme.ts index b6963e8b..2ef8ecbe 100644 --- a/packages/hooks/src/useTheme.ts +++ b/packages/hooks/src/useTheme.ts @@ -1,7 +1,7 @@ 'use client'; import { useContext } from 'react'; -import { YouVersionContext } from './context'; +import { YouVersionContext } from './context/YouVersionContext'; import { useHookOverride } from './useHookOverride'; /** diff --git a/packages/hooks/src/useVOTD.ts b/packages/hooks/src/useVOTD.ts index 83537d3a..6b3cdf1d 100644 --- a/packages/hooks/src/useVOTD.ts +++ b/packages/hooks/src/useVOTD.ts @@ -1,7 +1,9 @@ 'use client'; -import type { VOTD } from '@youversion/platform-core'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getVOTD, type VOTD } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseQueryResult } from './useQueryResult'; @@ -11,12 +13,13 @@ export type UseVerseOfTheDayResult = UseQueryResult; export function useVerseOfTheDay(day: number, options?: UseApiDataOptions): UseVerseOfTheDayResult { const override = useHookOverride('useVerseOfTheDay'); - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { data, loading, error, refetch } = useApiData( [...keyBase, 'votd', day], - () => bibleClient.getVOTD(day), + () => (bibleClient ? bibleClient.getVOTD(day) : getVOTD(apiClient, day)), { enabled: !override && options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useVerse.ts b/packages/hooks/src/useVerse.ts index 49dc1fee..94f863a8 100644 --- a/packages/hooks/src/useVerse.ts +++ b/packages/hooks/src/useVerse.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getVerse, type BibleVerse } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleVerse } from '@youversion/platform-core'; export type UseVerseResult = UseNamedQueryResult<'verse', BibleVerse>; @@ -15,7 +17,8 @@ export function useVerse( verse: number, options?: UseApiDataOptions, ): UseVerseResult { - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { @@ -25,7 +28,10 @@ export function useVerse( refetch, } = useApiData( [...keyBase, 'verse', versionId, book, chapter, verse], - () => bibleClient.getVerse(versionId, book, chapter, verse), + () => + bibleClient + ? bibleClient.getVerse(versionId, book, chapter, verse) + : getVerse(apiClient, versionId, book, chapter, verse), { enabled: options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useVerses.ts b/packages/hooks/src/useVerses.ts index 942f3bf4..371d45e7 100644 --- a/packages/hooks/src/useVerses.ts +++ b/packages/hooks/src/useVerses.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getVerses, type BibleVerse, type Collection } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleVerse, Collection } from '@youversion/platform-core'; export type UseVersesResult = UseNamedQueryResult<'verses', Collection>; @@ -14,7 +16,8 @@ export function useVerses( chapter: number, options?: UseApiDataOptions, ): UseVersesResult { - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { @@ -24,7 +27,10 @@ export function useVerses( refetch, } = useApiData>( [...keyBase, 'verses', versionId, book, chapter], - () => bibleClient.getVerses(versionId, book, chapter), + () => + bibleClient + ? bibleClient.getVerses(versionId, book, chapter) + : getVerses(apiClient, versionId, book, chapter), { enabled: options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useVersion.ts b/packages/hooks/src/useVersion.ts index 84d5bc53..9049dd3f 100644 --- a/packages/hooks/src/useVersion.ts +++ b/packages/hooks/src/useVersion.ts @@ -1,17 +1,20 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getVersion, type BibleVersion } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { BibleVersion } from '@youversion/platform-core'; import { useHookOverride } from './useHookOverride'; export type UseVersionResult = UseNamedQueryResult<'version', BibleVersion>; export function useVersion(versionId: number, options?: UseApiDataOptions): UseVersionResult { const override = useHookOverride('useVersion'); - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const { @@ -21,7 +24,7 @@ export function useVersion(versionId: number, options?: UseApiDataOptions): UseV refetch, } = useApiData( [...keyBase, 'version', versionId], - () => bibleClient.getVersion(versionId), + () => (bibleClient ? bibleClient.getVersion(versionId) : getVersion(apiClient, versionId)), { enabled: !override && options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/src/useVersions.ts b/packages/hooks/src/useVersions.ts index 28c6bef2..f338d17f 100644 --- a/packages/hooks/src/useVersions.ts +++ b/packages/hooks/src/useVersions.ts @@ -1,10 +1,12 @@ 'use client'; -import { useBibleClient } from './useBibleClient'; +import { useContext } from 'react'; +import { getVersions, type Collection, type BibleVersion } from '@youversion/platform-core'; +import { YouVersionContext } from './context/YouVersionContext'; +import { useApiClient } from './internal/useApiClient'; import { useApiData, type UseApiDataOptions } from './useApiData'; import { useQueryKeyBase } from './internal/useQueryKeyBase'; import type { UseNamedQueryResult } from './useQueryResult'; -import type { Collection, BibleVersion } from '@youversion/platform-core'; import { useHookOverride } from './useHookOverride'; export interface UseVersionsOptions extends UseApiDataOptions { @@ -26,7 +28,8 @@ export function useVersions( options?: UseVersionsOptions, ): UseVersionsResult { const override = useHookOverride('useVersions'); - const bibleClient = useBibleClient(); + const bibleClient = useContext(YouVersionContext)?.bibleClient; + const apiClient = useApiClient(); const keyBase = useQueryKeyBase(); const getVersionsOptions = @@ -64,7 +67,10 @@ export function useVersions( fieldsKey, options?.all_available, ], - () => bibleClient.getVersions(languageRanges, licenseId, getVersionsOptions), + () => + bibleClient + ? bibleClient.getVersions(languageRanges, licenseId, getVersionsOptions) + : getVersions(apiClient, languageRanges, licenseId, getVersionsOptions), { enabled: !override && options?.enabled !== false, keepPreviousData: options?.keepPreviousData, diff --git a/packages/hooks/tsup.config.ts b/packages/hooks/tsup.config.ts new file mode 100644 index 00000000..5136822e --- /dev/null +++ b/packages/hooks/tsup.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts', 'src/test-utils.tsx'], + format: ['cjs', 'esm'], + dts: false, + treeshake: true, + external: ['jsdom'], + // Whitespace only. Keep identifiers so a future stamp in this graph + // still matches `scripts/check-sdk-version-stamp.mjs`. + esbuildOptions(options) { + options.minifyWhitespace = true; + options.minifySyntax = false; + options.minifyIdentifiers = false; + }, +}); From 578d0a660cd22941693d79e4f14edc8cc02b9585 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 8 Sep 2026 13:06:26 -0500 Subject: [PATCH 2/4] chore(changeset): add hooks changeset for the narrow-import split --- .changeset/ype-5528-hooks-split.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ype-5528-hooks-split.md diff --git a/.changeset/ype-5528-hooks-split.md b/.changeset/ype-5528-hooks-split.md new file mode 100644 index 00000000..6462baad --- /dev/null +++ b/.changeset/ype-5528-hooks-split.md @@ -0,0 +1,5 @@ +--- +'@youversion/platform-react-hooks': patch +--- + +Hooks import `YouVersionContext` from its module, not the context barrel, so a `useChapter` import does not evaluate Auth. Hooks run tsup's Rollup tree-shake pass so unused exports drop from narrow graphs. Hooks wipe `dist` on build so stale tsup `.d.cts` files cannot publish. From 7b49356bf2610b21cd7009e4951f3bebb470df72 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Thu, 10 Sep 2026 09:14:13 -0500 Subject: [PATCH 3/4] fix(hooks): keep auth lazy component identity stable --- .../hooks/src/context/YouVersionProvider.tsx | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/packages/hooks/src/context/YouVersionProvider.tsx b/packages/hooks/src/context/YouVersionProvider.tsx index d8ab596f..a43ee037 100644 --- a/packages/hooks/src/context/YouVersionProvider.tsx +++ b/packages/hooks/src/context/YouVersionProvider.tsx @@ -68,26 +68,10 @@ interface YouVersionProviderPropsWithoutAuth extends YouVersionProviderPropsBase authRedirectUrl?: never; } -const loadAuthProvider = () => import('./YouVersionAuthProvider'); - -function YouVersionAuthGate({ - config, - userInfo, - children, -}: { - config: { appKey: string; apiHost?: string; redirectUri: string }; - userInfo?: YouVersionUserInfoJSON | null; - children: ReactNode; -}): React.ReactElement { - const AuthProvider = useMemo(() => lazy(loadAuthProvider), []); - return ( - - - {children} - - - ); -} +// Keep the lazy component at module scope. React may discard useMemo caches +// when a component suspends during its initial mount; recreating this lazy +// wrapper inside the provider can therefore leave the tree suspended forever. +const AuthProvider = lazy(() => import('./YouVersionAuthProvider')); function useResolvedTheme(theme: 'light' | 'dark' | 'system'): 'light' | 'dark' { const [resolved, setResolved] = useState<'light' | 'dark'>(() => { @@ -214,12 +198,14 @@ function YouVersionProviderInner( return ( - - {children} - + + + {children} + + ); From 421ecdfb9f686dda216a6d55d82227c676ff2c8f Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Fri, 11 Sep 2026 10:35:47 -0500 Subject: [PATCH 4/4] fix(hooks): address narrow bundle review feedback --- .size-limit.json | 2 +- .../hooks/src/context/YouVersionProvider.tsx | 22 ++++-- .../hooks/src/internal/versionFilterKey.ts | 9 ++- packages/hooks/src/useChapter.test.tsx | 28 +++---- packages/hooks/src/useLanguage.test.tsx | 28 +++---- packages/hooks/src/useLanguageClient.test.tsx | 77 ------------------- packages/hooks/src/useLanguageClient.ts | 13 ---- packages/hooks/tsup.config.ts | 3 +- scripts/check-tree-shaking.mjs | 2 + 9 files changed, 52 insertions(+), 132 deletions(-) delete mode 100644 packages/hooks/src/useLanguageClient.test.tsx delete mode 100644 packages/hooks/src/useLanguageClient.ts diff --git a/.size-limit.json b/.size-limit.json index 92c24efd..e3578667 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -34,7 +34,7 @@ "name": "hooks / useChapter only", "path": "packages/hooks/dist/index.js", "import": "{ useChapter }", - "limit": "64 KB", + "limit": "12.2 KB", "ignore": ["react", "react-dom", "@tanstack/react-query"] }, { diff --git a/packages/hooks/src/context/YouVersionProvider.tsx b/packages/hooks/src/context/YouVersionProvider.tsx index a43ee037..c55449d8 100644 --- a/packages/hooks/src/context/YouVersionProvider.tsx +++ b/packages/hooks/src/context/YouVersionProvider.tsx @@ -1,9 +1,10 @@ 'use client'; -import type { PropsWithChildren, ReactNode } from 'react'; +import type { LazyExoticComponent, PropsWithChildren, ReactNode } from 'react'; import { lazy, Suspense, useEffect, useMemo, useState } from 'react'; import { QueryClient } from '@tanstack/react-query'; import { YouVersionContext } from './YouVersionContext'; +import type YouVersionAuthProvider from './YouVersionAuthProvider'; import { serializeAdditionalHeaders } from '../internal/additionalHeadersKey'; import { InternalQueryClientProvider } from '../internal/QueryClientContext'; import { queryClientDefaultOptions } from '../internal/queryClientDefaults'; @@ -19,12 +20,16 @@ interface YouVersionProviderPropsBase { theme?: 'light' | 'dark' | 'system'; /** * Integrator display name for the sign-in dialog body copy. Synced onto - * `YouVersionPlatformConfiguration.appName`. + * `YouVersionPlatformConfiguration.appName`. The UI package also mirrors this + * onto its bundled core copy (tsup `noExternal`), so pass it via + * `YouVersionProvider` props — do not set the config from a separate + * `@youversion/platform-core` import when consuming `@youversion/platform-react-ui`. */ appName?: string; /** * Optional pitch line for the sign-in dialog. Synced onto - * `YouVersionPlatformConfiguration.signInPromptMessage`. + * `YouVersionPlatformConfiguration.signInPromptMessage` (and mirrored by the + * UI provider onto its bundled core copy — same dual-instance caveat as `appName`). */ signInPromptMessage?: string; /** @@ -68,10 +73,12 @@ interface YouVersionProviderPropsWithoutAuth extends YouVersionProviderPropsBase authRedirectUrl?: never; } -// Keep the lazy component at module scope. React may discard useMemo caches -// when a component suspends during its initial mount; recreating this lazy -// wrapper inside the provider can therefore leave the tree suspended forever. -const AuthProvider = lazy(() => import('./YouVersionAuthProvider')); +let authProvider: LazyExoticComponent | undefined; + +function getAuthProvider(): LazyExoticComponent { + authProvider ??= lazy(() => import('./YouVersionAuthProvider')); + return authProvider; +} function useResolvedTheme(theme: 'light' | 'dark' | 'system'): 'light' | 'dark' { const [resolved, setResolved] = useState<'light' | 'dark'>(() => { @@ -195,6 +202,7 @@ function YouVersionProviderInner( }; if (includeAuth) { + const AuthProvider = getAuthProvider(); return ( diff --git a/packages/hooks/src/internal/versionFilterKey.ts b/packages/hooks/src/internal/versionFilterKey.ts index ac0c23f4..042b20a2 100644 --- a/packages/hooks/src/internal/versionFilterKey.ts +++ b/packages/hooks/src/internal/versionFilterKey.ts @@ -4,10 +4,11 @@ import { getVersionFilterSnapshot } from '@youversion/platform-core'; * @internal * Converts the active Bible version filter to stable text. * - * `YouVersionProvider` writes those lists onto - * `YouVersionPlatformConfiguration` during render, before children read this - * value. Readers use `getVersionFilterSnapshot` so unused auth storage stays - * off the `useChapter` graph. + * `YouVersionProvider` writes its `permittedVersionIds`, `excludedVersionIds`, + * and `permittedLanguageTags` props onto `YouVersionPlatformConfiguration` + * during render, before children read this value. Readers use + * `getVersionFilterSnapshot` so unused auth storage stays off the `useChapter` + * graph. * * The ids and tags are sorted before serialization, so two lists with the same * entries produce the same text. An unset list serializes as `null`, which keeps diff --git a/packages/hooks/src/useChapter.test.tsx b/packages/hooks/src/useChapter.test.tsx index e84f0604..5616846f 100644 --- a/packages/hooks/src/useChapter.test.tsx +++ b/packages/hooks/src/useChapter.test.tsx @@ -36,20 +36,6 @@ describe('useChapter', () => { expect.soft(result.current.chapter).toEqual(mockChapter); }); - it('fetches via getChapter when the provider has no bibleClient override', async () => { - const spy = vi.spyOn(core, 'getChapter').mockResolvedValue(mockChapter); - const bare = createYVWrapper('test-app-key'); - const { result } = renderHook(() => useChapter(111, 'MAT', 1), { wrapper: bare }); - - await waitFor(() => { - expect(result.current.loading).toBe(false); - }); - - expect(spy).toHaveBeenCalled(); - expect(result.current.chapter).toEqual(mockChapter); - spy.mockRestore(); - }); - it.each([ { param: 'versionId', @@ -175,6 +161,20 @@ describe('useChapter', () => { }); }); + it('fetches via getChapter when the provider has no bibleClient override', async () => { + const spy = vi.spyOn(core, 'getChapter').mockResolvedValue(mockChapter); + const bare = createYVWrapper('test-app-key'); + const { result } = renderHook(() => useChapter(111, 'MAT', 1), { wrapper: bare }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(spy).toHaveBeenCalledWith(expect.any(core.ApiClient), 111, 'MAT', 1); + expect(result.current.chapter).toEqual(mockChapter); + spy.mockRestore(); + }); + it('should serve the cached chapter instantly on revisit and revalidate in background', async () => { // The acceptance criterion behind the TanStack Query migration: leaving // a chapter and coming back renders it from cache with no loading diff --git a/packages/hooks/src/useLanguage.test.tsx b/packages/hooks/src/useLanguage.test.tsx index c53c8538..18f4e7b7 100644 --- a/packages/hooks/src/useLanguage.test.tsx +++ b/packages/hooks/src/useLanguage.test.tsx @@ -45,20 +45,6 @@ describe('useLanguage', () => { expect.soft(result.current.language).toEqual(mockLanguage); }); - it('fetches via getLanguage when the provider has no languagesClient override', async () => { - const spy = vi.spyOn(core, 'getLanguage').mockResolvedValue(mockLanguage); - const bare = createYVWrapper('test-app-key'); - const { result } = renderHook(() => useLanguage('en'), { wrapper: bare }); - - await waitFor(() => { - expect(result.current.loading).toBe(false); - }); - - expect(spy).toHaveBeenCalled(); - expect(result.current.language).toEqual(mockLanguage); - spy.mockRestore(); - }); - it('should refetch when languageId changes', async () => { const { result, rerender } = renderHook(({ languageId }) => useLanguage(languageId), { wrapper, @@ -124,4 +110,18 @@ describe('useLanguage', () => { }); }); }); + + it('fetches via getLanguage when the provider has no languagesClient override', async () => { + const spy = vi.spyOn(core, 'getLanguage').mockResolvedValue(mockLanguage); + const bare = createYVWrapper('test-app-key'); + const { result } = renderHook(() => useLanguage('en'), { wrapper: bare }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(spy).toHaveBeenCalledWith(expect.any(core.ApiClient), 'en'); + expect(result.current.language).toEqual(mockLanguage); + spy.mockRestore(); + }); }); diff --git a/packages/hooks/src/useLanguageClient.test.tsx b/packages/hooks/src/useLanguageClient.test.tsx deleted file mode 100644 index b37c39e2..00000000 --- a/packages/hooks/src/useLanguageClient.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; -import type { ReactNode } from 'react'; -import { useLanguagesClient } from './useLanguageClient'; -import { YouVersionContext } from './context'; -import { LanguagesClient } from '@youversion/platform-core'; -import { createLanguagesClientStub, createYVWrapper } from './test/utils'; - -describe('useLanguagesClient', () => { - describe('context validation', () => { - it('should throw error when context is not provided', () => { - expect(() => renderHook(() => useLanguagesClient())).toThrow( - 'YouVersion context not found. Make sure your component is wrapped with YouVersionProvider and an API key is provided.', - ); - }); - - it('should throw error when appKey is missing', () => { - const wrapper = ({ children }: { children: ReactNode }) => ( - {children} - ); - - expect(() => renderHook(() => useLanguagesClient(), { wrapper })).toThrow( - 'YouVersion context not found. Make sure your component is wrapped with YouVersionProvider and an API key is provided.', - ); - }); - }); - - describe('client creation', () => { - it('should create a LanguagesClient when context is valid', () => { - const wrapper = createYVWrapper(); - const { result } = renderHook(() => useLanguagesClient(), { wrapper }); - - expect(result.current).toBeInstanceOf(LanguagesClient); - }); - - it('should return the injected LanguagesClient when present', () => { - const languagesClient = createLanguagesClientStub({}); - const wrapper = createYVWrapper('test-app-key', { languagesClient }); - const { result } = renderHook(() => useLanguagesClient(), { wrapper }); - - expect(result.current).toBe(languagesClient); - }); - - it('should memoize LanguagesClient instance', () => { - const wrapper = createYVWrapper(); - const { result, rerender } = renderHook(() => useLanguagesClient(), { wrapper }); - const firstClient = result.current; - - rerender(); - - expect(result.current).toBe(firstClient); - }); - - it('should create new LanguagesClient when context values change', () => { - let currentAppKey = 'test-app-key'; - - const wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - - ); - - const { result, rerender } = renderHook(() => useLanguagesClient(), { wrapper }); - const firstClient = result.current; - - currentAppKey = 'new-app-key'; - rerender(); - - expect(result.current).not.toBe(firstClient); - expect(result.current).toBeInstanceOf(LanguagesClient); - }); - }); -}); diff --git a/packages/hooks/src/useLanguageClient.ts b/packages/hooks/src/useLanguageClient.ts deleted file mode 100644 index 11c029cb..00000000 --- a/packages/hooks/src/useLanguageClient.ts +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; - -import { useContext, useMemo } from 'react'; -import { LanguagesClient } from '@youversion/platform-core'; -import { YouVersionContext } from './context/YouVersionContext'; -import { useApiClient } from './internal/useApiClient'; - -export function useLanguagesClient(): LanguagesClient { - const override = useContext(YouVersionContext)?.languagesClient; - const apiClient = useApiClient(); - const constructed = useMemo(() => new LanguagesClient(apiClient), [apiClient]); - return override ?? constructed; -} diff --git a/packages/hooks/tsup.config.ts b/packages/hooks/tsup.config.ts index 5136822e..e9601754 100644 --- a/packages/hooks/tsup.config.ts +++ b/packages/hooks/tsup.config.ts @@ -6,8 +6,7 @@ export default defineConfig({ dts: false, treeshake: true, external: ['jsdom'], - // Whitespace only. Keep identifiers so a future stamp in this graph - // still matches `scripts/check-sdk-version-stamp.mjs`. + // Apply whitespace-only minification; preserve syntax and identifiers. esbuildOptions(options) { options.minifyWhitespace = true; options.minifySyntax = false; diff --git a/scripts/check-tree-shaking.mjs b/scripts/check-tree-shaking.mjs index e4381bbd..af535fa5 100644 --- a/scripts/check-tree-shaking.mjs +++ b/scripts/check-tree-shaking.mjs @@ -145,6 +145,7 @@ export { absent: [ 'A redirect URL is required to start sign-in for highlights.', 'YouVersion context is required to start a data exchange.', + 'youversion-platform:granted-permissions', ], }, controls: [ @@ -154,6 +155,7 @@ export { present: [ 'A redirect URL is required to start sign-in for highlights.', 'YouVersion context is required to start a data exchange.', + 'youversion-platform:granted-permissions', ], }, ],