fix: resolve SDK v3 mount errors, back button and fullscreen not working

- Обернуть каждый mount-вызов в safeMountSync() для защиты от
  ConcurrentCallError при HMR/повторной инициализации
- Перенести requestFullscreen() внутрь mountViewport().then() — fullscreen
  требует смонтированный viewport, ранее вызывался через dynamic import
  с setTimeout до завершения mount
- Стабилизировать обработчик BackButton через useRef + useCallback —
  navigate меняется каждый рендер, что вызывало постоянную
  пере-подписку и потерю обработчика
This commit is contained in:
c0mrade
2026-02-04 11:18:49 +03:00
parent 023c3ef6b3
commit 61e3910981
2 changed files with 46 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react'; import { useEffect, useRef, useCallback } from 'react';
import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom'; import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom';
import { import {
showBackButton, showBackButton,
@@ -21,6 +21,8 @@ import { isInTelegramWebApp } from './hooks/useTelegramSDK';
function TelegramBackButton() { function TelegramBackButton() {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const navigateRef = useRef(navigate);
navigateRef.current = navigate;
useEffect(() => { useEffect(() => {
const isRoot = location.pathname === '/' || location.pathname === ''; const isRoot = location.pathname === '/' || location.pathname === '';
@@ -31,25 +33,29 @@ function TelegramBackButton() {
showBackButton(); showBackButton();
} }
} catch { } catch {
// Not in Telegram or back button not mounted // Back button not mounted
} }
}, [location]); }, [location]);
// Stable handler — ref prevents re-subscription on every render
const handler = useCallback(() => {
navigateRef.current(-1);
}, []);
useEffect(() => { useEffect(() => {
const handler = () => navigate(-1);
try { try {
onBackButtonClick(handler); onBackButtonClick(handler);
} catch { } catch {
// Not in Telegram // Back button not mounted
} }
return () => { return () => {
try { try {
offBackButtonClick(handler); offBackButtonClick(handler);
} catch { } catch {
// Not in Telegram // Back button not mounted
} }
}; };
}, [navigate]); }, [handler]);
return null; return null;
} }

View File

@@ -17,6 +17,8 @@ import {
mountMainButton, mountMainButton,
bindThemeParamsCssVars, bindThemeParamsCssVars,
bindViewportCssVars, bindViewportCssVars,
requestFullscreen,
isFullscreen,
} from '@telegram-apps/sdk-react'; } from '@telegram-apps/sdk-react';
import { AppWithNavigator } from './AppWithNavigator'; import { AppWithNavigator } from './AppWithNavigator';
import { initLogoPreload } from './api/branding'; import { initLogoPreload } from './api/branding';
@@ -24,40 +26,52 @@ import { getCachedFullscreenEnabled, isTelegramMobile } from './hooks/useTelegra
import './i18n'; import './i18n';
import './styles/globals.css'; 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 // Initialize Telegram SDK v3
try { try {
init(); init();
restoreInitData(); restoreInitData();
mountMiniApp(); safeMountSync(() => mountMiniApp());
safeMountSync(() => {
mountThemeParams(); mountThemeParams();
bindThemeParamsCssVars(); bindThemeParamsCssVars();
});
safeMountSync(() => {
mountSwipeBehavior(); mountSwipeBehavior();
disableVerticalSwipes(); disableVerticalSwipes();
});
safeMountSync(() => {
mountClosingBehavior(); mountClosingBehavior();
disableClosingConfirmation(); disableClosingConfirmation();
mountBackButton(); });
mountMainButton(); safeMountSync(() => mountBackButton());
safeMountSync(() => mountMainButton());
// Viewport — async, fullscreen зависит от смонтированного viewport
mountViewport() mountViewport()
.then(() => { .then(() => {
bindViewportCssVars(); bindViewportCssVars();
expandViewport(); expandViewport();
// Auto-enter fullscreen if enabled in settings (mobile only)
if (getCachedFullscreenEnabled() && isTelegramMobile()) {
if (!isFullscreen()) {
requestFullscreen();
}
}
}) })
.catch(() => {}); .catch(() => {});
miniAppReady(); 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 { } catch {
// Not in Telegram — ok // Not in Telegram — ok
} }