diff --git a/.changeset/honest-dragons-lead.md b/.changeset/honest-dragons-lead.md
new file mode 100644
index 00000000..68908f77
--- /dev/null
+++ b/.changeset/honest-dragons-lead.md
@@ -0,0 +1,7 @@
+---
+'@youversion/platform-react-ui': patch
+'@youversion/platform-core': patch
+'@youversion/platform-react-hooks': patch
+---
+
+Fix user settings from localStorage not loading in the bible reader
diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx
index cf67319c..d4fee283 100644
--- a/packages/ui/src/components/bible-reader.stories.tsx
+++ b/packages/ui/src/components/bible-reader.stories.tsx
@@ -497,6 +497,62 @@ export const AuthenticatedWithAvatar: Story = {
},
};
+export const LoadsSavedPreferencesFromLocalStorage: Story = {
+ tags: ['integration'],
+ args: {
+ versionId: 111,
+ book: 'JHN',
+ chapter: '1',
+ background: 'light',
+ },
+ beforeEach: () => {
+ localStorage.clear();
+ // Pre-populate localStorage with saved preferences
+ localStorage.setItem('youversion-platform:reader:font-size', '18');
+ localStorage.setItem('youversion-platform:reader:font-family', 'Source Serif');
+ },
+ render: (args) => (
+
+
+
+
+
+
+ ),
+ play: async ({ canvasElement }) => {
+ await waitFor(
+ async () => {
+ const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]');
+ await expect(verseContainer).toBeInTheDocument();
+ },
+ { timeout: 5000 },
+ );
+
+ // Verify the saved settings were applied via CSS variables
+ const verseContainer = canvasElement.querySelector(
+ '[data-slot="yv-bible-renderer"]',
+ )!;
+ await expect(verseContainer.style.getPropertyValue('--yv-reader-font-size')).toBe('18px');
+ await expect(verseContainer.style.getPropertyValue('--yv-reader-font-family')).toBe(
+ 'Source Serif',
+ );
+
+ // Open settings and verify the correct font family button is active
+ const settingsButton = screen.getByRole('button', { name: /settings/i });
+ await userEvent.click(settingsButton);
+
+ await waitFor(async () => {
+ await expect(await screen.findByText('Reader Settings')).toBeInTheDocument();
+ });
+
+ const sourceSerifButton = screen.getByRole('button', { name: /source serif/i });
+ await expect(sourceSerifButton).toHaveClass('yv:bg-black');
+
+ const interButton = screen.getByRole('button', { name: /inter/i });
+ await expect(interButton).not.toHaveClass('yv:bg-black');
+ },
+};
+
export const AuthenticatedWithoutAvatar: Story = {
tags: ['integration'],
args: {
diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx
index b1847274..28a910e6 100644
--- a/packages/ui/src/components/bible-reader.tsx
+++ b/packages/ui/src/components/bible-reader.tsx
@@ -1,6 +1,14 @@
'use client';
-import { createContext, useContext, useMemo, useState, useEffect, type ReactNode } from 'react';
+import {
+ createContext,
+ useContext,
+ useMemo,
+ useState,
+ useEffect,
+ useLayoutEffect,
+ type ReactNode,
+} from 'react';
import { useControllableState } from '@radix-ui/react-use-controllable-state';
import { useBooks, useVersion, useTheme, useYVAuth } from '@youversion/platform-react-hooks';
import { BibleChapterPicker } from './bible-chapter-picker';
@@ -96,26 +104,34 @@ function Root({
onChange: onVersionChange,
});
- const [currentFontSize, setCurrentFontSize] = useState(() => {
- const validatedFontSize =
- fontSize > MAX_FONT_SIZE || fontSize < MIN_FONT_SIZE ? DEFAULT_FONT_SIZE : fontSize;
- if (typeof window === 'undefined') return validatedFontSize;
- const size = localStorage.getItem('youversion-platform:reader:font-size');
- return size ? parseInt(size) : validatedFontSize;
- });
+ const validatedFontSize =
+ fontSize > MAX_FONT_SIZE || fontSize < MIN_FONT_SIZE ? DEFAULT_FONT_SIZE : fontSize;
- const [currentFontFamily, setCurrentFontFamily] = useState(() => {
- if (typeof window === 'undefined') return fontFamily;
- return localStorage.getItem('youversion-platform:reader:font-family') || fontFamily;
- });
+ const [currentFontSize, setCurrentFontSize] = useState(validatedFontSize);
+ const [currentFontFamily, setCurrentFontFamily] = useState(fontFamily);
+
+ // Load saved preferences from localStorage before paint (avoids flash of default values)
+ useLayoutEffect(() => {
+ const savedFontSize = localStorage.getItem('youversion-platform:reader:font-size');
+ if (savedFontSize) {
+ const parsed = parseInt(savedFontSize);
+ if (parsed >= MIN_FONT_SIZE && parsed <= MAX_FONT_SIZE) {
+ setCurrentFontSize(parsed);
+ }
+ }
+
+ const savedFontFamily = localStorage.getItem('youversion-platform:reader:font-family');
+ if (savedFontFamily) {
+ setCurrentFontFamily(savedFontFamily);
+ }
+ }, []);
+ // Save preferences to localStorage when they change
useEffect(() => {
- if (typeof window === 'undefined') return;
localStorage.setItem('youversion-platform:reader:font-size', currentFontSize.toString());
}, [currentFontSize]);
useEffect(() => {
- if (typeof window === 'undefined') return;
localStorage.setItem('youversion-platform:reader:font-family', currentFontFamily);
}, [currentFontFamily]);