diff --git a/src/AppWithNavigator.tsx b/src/AppWithNavigator.tsx index 11ae689..33ae2a3 100644 --- a/src/AppWithNavigator.tsx +++ b/src/AppWithNavigator.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useRef, useCallback } from 'react'; import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom'; import { showBackButton, @@ -21,6 +21,8 @@ import { isInTelegramWebApp } from './hooks/useTelegramSDK'; function TelegramBackButton() { const location = useLocation(); const navigate = useNavigate(); + const navigateRef = useRef(navigate); + navigateRef.current = navigate; useEffect(() => { const isRoot = location.pathname === '/' || location.pathname === ''; @@ -31,25 +33,29 @@ function TelegramBackButton() { showBackButton(); } } catch { - // Not in Telegram or back button not mounted + // Back button not mounted } }, [location]); + // Stable handler — ref prevents re-subscription on every render + const handler = useCallback(() => { + navigateRef.current(-1); + }, []); + useEffect(() => { - const handler = () => navigate(-1); try { onBackButtonClick(handler); } catch { - // Not in Telegram + // Back button not mounted } return () => { try { offBackButtonClick(handler); } catch { - // Not in Telegram + // Back button not mounted } }; - }, [navigate]); + }, [handler]); return null; } diff --git a/src/main.tsx b/src/main.tsx index d4ee76d..1e60f53 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -17,6 +17,8 @@ import { mountMainButton, bindThemeParamsCssVars, bindViewportCssVars, + requestFullscreen, + isFullscreen, } from '@telegram-apps/sdk-react'; import { AppWithNavigator } from './AppWithNavigator'; import { initLogoPreload } from './api/branding'; @@ -24,40 +26,52 @@ import { getCachedFullscreenEnabled, isTelegramMobile } from './hooks/useTelegra import './i18n'; import './styles/globals.css'; +// Safe mount helper — ignores "already mounted/mounting" errors (HMR, StrictMode) +function safeMountSync(fn: () => void) { + try { + fn(); + } catch { + // Already mounted or not available + } +} + // Initialize Telegram SDK v3 try { init(); restoreInitData(); - mountMiniApp(); - mountThemeParams(); - bindThemeParamsCssVars(); - mountSwipeBehavior(); - disableVerticalSwipes(); - mountClosingBehavior(); - disableClosingConfirmation(); - mountBackButton(); - mountMainButton(); + safeMountSync(() => mountMiniApp()); + safeMountSync(() => { + mountThemeParams(); + bindThemeParamsCssVars(); + }); + safeMountSync(() => { + mountSwipeBehavior(); + disableVerticalSwipes(); + }); + safeMountSync(() => { + mountClosingBehavior(); + disableClosingConfirmation(); + }); + safeMountSync(() => mountBackButton()); + safeMountSync(() => mountMainButton()); + // Viewport — async, fullscreen зависит от смонтированного viewport mountViewport() .then(() => { bindViewportCssVars(); expandViewport(); + + // Auto-enter fullscreen if enabled in settings (mobile only) + if (getCachedFullscreenEnabled() && isTelegramMobile()) { + if (!isFullscreen()) { + requestFullscreen(); + } + } }) .catch(() => {}); miniAppReady(); - - // Auto-enter fullscreen if enabled in settings (mobile only) - if (getCachedFullscreenEnabled() && isTelegramMobile()) { - import('@telegram-apps/sdk-react').then(({ requestFullscreen, isFullscreen }) => { - setTimeout(() => { - if (!isFullscreen()) { - requestFullscreen(); - } - }, 100); - }); - } } catch { // Not in Telegram — ok }