mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
perf: add Zustand selectors to prevent cascading re-renders
Replace all bare useAuthStore(), useBlockingStore(), and useSuccessNotification() calls with individual field selectors. This prevents components from re-rendering when unrelated store fields change. - 21 useAuthStore usages across 20 files: individual selectors for 1-2 fields, useShallow for 3+ fields - 5 useBlockingStore usages: individual selectors - 2 useSuccessNotification usages: individual selectors
This commit is contained in:
@@ -90,7 +90,8 @@ const AdminPinnedMessageCreate = lazy(() => import('./pages/AdminPinnedMessageCr
|
|||||||
const AdminEmailTemplatePreview = lazy(() => import('./pages/AdminEmailTemplatePreview'));
|
const AdminEmailTemplatePreview = lazy(() => import('./pages/AdminEmailTemplatePreview'));
|
||||||
|
|
||||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||||
const { isAuthenticated, isLoading } = useAuthStore();
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
const isLoading = useAuthStore((state) => state.isLoading);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -107,7 +108,9 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AdminRoute({ children }: { children: React.ReactNode }) {
|
function AdminRoute({ children }: { children: React.ReactNode }) {
|
||||||
const { isAuthenticated, isLoading, isAdmin } = useAuthStore();
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
const isLoading = useAuthStore((state) => state.isLoading);
|
||||||
|
const isAdmin = useAuthStore((state) => state.isAdmin);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -133,7 +136,7 @@ function LazyPage({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BlockingOverlay() {
|
function BlockingOverlay() {
|
||||||
const { blockingType } = useBlockingStore();
|
const blockingType = useBlockingStore((state) => state.blockingType);
|
||||||
|
|
||||||
if (blockingType === 'maintenance') {
|
if (blockingType === 'maintenance') {
|
||||||
return <MaintenanceScreen />;
|
return <MaintenanceScreen />;
|
||||||
|
|||||||
@@ -78,7 +78,9 @@ const CloseIcon = () => (
|
|||||||
export default function SuccessNotificationModal() {
|
export default function SuccessNotificationModal() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isOpen, data, hide } = useSuccessNotification();
|
const isOpen = useSuccessNotification((state) => state.isOpen);
|
||||||
|
const data = useSuccessNotification((state) => state.data);
|
||||||
|
const hide = useSuccessNotification((state) => state.hide);
|
||||||
const { formatAmount, currencySymbol } = useCurrency();
|
const { formatAmount, currencySymbol } = useCurrency();
|
||||||
const { safeAreaInset, contentSafeAreaInset, isTelegramWebApp } = useTelegramSDK();
|
const { safeAreaInset, contentSafeAreaInset, isTelegramWebApp } = useTelegramSDK();
|
||||||
const haptic = useHaptic();
|
const haptic = useHaptic();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function TicketNotificationBell({ isAdmin = false }: TicketNotifi
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { isAuthenticated } = useAuthStore();
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ export default function WebSocketNotifications() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const { refreshUser } = useAuthStore();
|
const refreshUser = useAuthStore((state) => state.refreshUser);
|
||||||
const { formatAmount, currencySymbol } = useCurrency();
|
const { formatAmount, currencySymbol } = useCurrency();
|
||||||
const { show: showSuccessModal } = useSuccessNotification();
|
const showSuccessModal = useSuccessNotification((state) => state.show);
|
||||||
|
|
||||||
const handleMessage = useCallback(
|
const handleMessage = useCallback(
|
||||||
(message: WSMessage) => {
|
(message: WSMessage) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useBlockingStore } from '../../store/blocking';
|
|||||||
|
|
||||||
export default function BlacklistedScreen() {
|
export default function BlacklistedScreen() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { blacklistedInfo } = useBlockingStore();
|
const blacklistedInfo = useBlockingStore((state) => state.blacklistedInfo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ const CHECK_COOLDOWN_SECONDS = 5;
|
|||||||
|
|
||||||
export default function ChannelSubscriptionScreen() {
|
export default function ChannelSubscriptionScreen() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { channelInfo, clearBlocking } = useBlockingStore();
|
const channelInfo = useBlockingStore((state) => state.channelInfo);
|
||||||
|
const clearBlocking = useBlockingStore((state) => state.clearBlocking);
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
const [cooldown, setCooldown] = useState(0);
|
const [cooldown, setCooldown] = useState(0);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useBlockingStore } from '../../store/blocking';
|
|||||||
|
|
||||||
export default function MaintenanceScreen() {
|
export default function MaintenanceScreen() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { maintenanceInfo } = useBlockingStore();
|
const maintenanceInfo = useBlockingStore((state) => state.maintenanceInfo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { initDataUser } from '@telegram-apps/sdk-react';
|
import { initDataUser } from '@telegram-apps/sdk-react';
|
||||||
|
|
||||||
import { useAuthStore } from '@/store/auth';
|
import { useAuthStore } from '@/store/auth';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { useTheme } from '@/hooks/useTheme';
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
import { usePlatform } from '@/platform';
|
import { usePlatform } from '@/platform';
|
||||||
import {
|
import {
|
||||||
@@ -77,7 +78,9 @@ export function AppHeader({
|
|||||||
}: AppHeaderProps) {
|
}: AppHeaderProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { user, logout, isAdmin } = useAuthStore();
|
const { user, logout, isAdmin } = useAuthStore(
|
||||||
|
useShallow((state) => ({ user: state.user, logout: state.logout, isAdmin: state.isAdmin })),
|
||||||
|
);
|
||||||
const { toggleTheme, isDark } = useTheme();
|
const { toggleTheme, isDark } = useTheme();
|
||||||
const { haptic, platform } = usePlatform();
|
const { haptic, platform } = usePlatform();
|
||||||
const [userPhotoUrl, setUserPhotoUrl] = useState<string | null>(null);
|
const [userPhotoUrl, setUserPhotoUrl] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ interface AppShellProps {
|
|||||||
export function AppShell({ children }: AppShellProps) {
|
export function AppShell({ children }: AppShellProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { isAdmin, logout } = useAuthStore();
|
const isAdmin = useAuthStore((state) => state.isAdmin);
|
||||||
|
const logout = useAuthStore((state) => state.logout);
|
||||||
const { isFullscreen, safeAreaInset, contentSafeAreaInset, platform, isMobile } =
|
const { isFullscreen, safeAreaInset, contentSafeAreaInset, platform, isMobile } =
|
||||||
useTelegramSDK();
|
useTelegramSDK();
|
||||||
const haptic = useHaptic();
|
const haptic = useHaptic();
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export function DesktopSidebar({
|
|||||||
}: DesktopSidebarProps) {
|
}: DesktopSidebarProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { user, logout } = useAuthStore();
|
const user = useAuthStore((state) => state.user);
|
||||||
|
const logout = useAuthStore((state) => state.logout);
|
||||||
const { haptic } = usePlatform();
|
const { haptic } = usePlatform();
|
||||||
|
|
||||||
// Branding
|
// Branding
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function CommandPalette({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { haptic } = usePlatform();
|
const { haptic } = usePlatform();
|
||||||
const { isAdmin } = useAuthStore();
|
const isAdmin = useAuthStore((state) => state.isAdmin);
|
||||||
const { toggleTheme, isDark } = useTheme();
|
const { toggleTheme, isDark } = useTheme();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { contestsApi } from '@/api/contests';
|
|||||||
import { pollsApi } from '@/api/polls';
|
import { pollsApi } from '@/api/polls';
|
||||||
|
|
||||||
export function useFeatureFlags() {
|
export function useFeatureFlags() {
|
||||||
const { isAuthenticated } = useAuthStore();
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
|
||||||
const { data: referralTerms } = useQuery({
|
const { data: referralTerms } = useQuery({
|
||||||
queryKey: ['referral-terms'],
|
queryKey: ['referral-terms'],
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const WalletIcon = ({ className = 'h-8 w-8' }: { className?: string }) => (
|
|||||||
|
|
||||||
export default function Balance() {
|
export default function Balance() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { refreshUser } = useAuthStore();
|
const refreshUser = useAuthStore((state) => state.refreshUser);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { formatAmount, currencySymbol } = useCurrency();
|
const { formatAmount, currencySymbol } = useCurrency();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import InstallationGuide from '../components/connection/InstallationGuide';
|
|||||||
export default function Connection() {
|
export default function Connection() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user, isAdmin } = useAuthStore();
|
const user = useAuthStore((state) => state.user);
|
||||||
|
const isAdmin = useAuthStore((state) => state.isAdmin);
|
||||||
const { isTelegramWebApp } = useTelegramSDK();
|
const { isTelegramWebApp } = useTelegramSDK();
|
||||||
const { impact: hapticImpact } = useHaptic();
|
const { impact: hapticImpact } = useHaptic();
|
||||||
|
|
||||||
|
|||||||
@@ -48,14 +48,15 @@ const RefreshIcon = ({ className = 'w-4 h-4' }: { className?: string }) => (
|
|||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user, refreshUser } = useAuthStore();
|
const user = useAuthStore((state) => state.user);
|
||||||
|
const refreshUser = useAuthStore((state) => state.refreshUser);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { formatAmount, currencySymbol, formatPositive } = useCurrency();
|
const { formatAmount, currencySymbol, formatPositive } = useCurrency();
|
||||||
const [trialError, setTrialError] = useState<string | null>(null);
|
const [trialError, setTrialError] = useState<string | null>(null);
|
||||||
const { isCompleted: isOnboardingCompleted, complete: completeOnboarding } = useOnboarding();
|
const { isCompleted: isOnboardingCompleted, complete: completeOnboarding } = useOnboarding();
|
||||||
const [showOnboarding, setShowOnboarding] = useState(false);
|
const [showOnboarding, setShowOnboarding] = useState(false);
|
||||||
const { blockingType } = useBlockingStore();
|
const blockingType = useBlockingStore((state) => state.blockingType);
|
||||||
|
|
||||||
// Refresh user data on mount
|
// Refresh user data on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate, useLocation } from 'react-router';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useAuthStore } from '../store/auth';
|
import { useAuthStore } from '../store/auth';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { authApi } from '../api/auth';
|
import { authApi } from '../api/auth';
|
||||||
import { isValidEmail } from '../utils/validation';
|
import { isValidEmail } from '../utils/validation';
|
||||||
import {
|
import {
|
||||||
@@ -32,7 +33,15 @@ export default function Login() {
|
|||||||
loginWithTelegram,
|
loginWithTelegram,
|
||||||
loginWithEmail,
|
loginWithEmail,
|
||||||
registerWithEmail,
|
registerWithEmail,
|
||||||
} = useAuthStore();
|
} = useAuthStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
isAuthenticated: state.isAuthenticated,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
loginWithTelegram: state.loginWithTelegram,
|
||||||
|
loginWithEmail: state.loginWithEmail,
|
||||||
|
registerWithEmail: state.registerWithEmail,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
// Get referral code from localStorage (captured from ?ref= param at module level in auth store)
|
// Get referral code from localStorage (captured from ?ref= param at module level in auth store)
|
||||||
const referralCode = getPendingReferralCode() || '';
|
const referralCode = getPendingReferralCode() || '';
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export default function OAuthCallback() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const { loginWithOAuth, isAuthenticated } = useAuthStore();
|
const loginWithOAuth = useAuthStore((state) => state.loginWithOAuth);
|
||||||
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ const PencilIcon = () => (
|
|||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user, setUser } = useAuthStore();
|
const user = useAuthStore((state) => state.user);
|
||||||
|
const setUser = useAuthStore((state) => state.setUser);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export default function TelegramCallback() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const { loginWithTelegramWidget, isAuthenticated } = useAuthStore();
|
const loginWithTelegramWidget = useAuthStore((state) => state.loginWithTelegramWidget);
|
||||||
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useAuthStore } from '../store/auth';
|
import { useAuthStore } from '../store/auth';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { brandingApi } from '../api/branding';
|
import { brandingApi } from '../api/branding';
|
||||||
import { isInTelegramWebApp, getTelegramInitData } from '../hooks/useTelegramSDK';
|
import { isInTelegramWebApp, getTelegramInitData } from '../hooks/useTelegramSDK';
|
||||||
|
|
||||||
@@ -33,7 +34,17 @@ export default function TelegramRedirect() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { loginWithTelegram, isAuthenticated, isLoading: authLoading } = useAuthStore();
|
const {
|
||||||
|
loginWithTelegram,
|
||||||
|
isAuthenticated,
|
||||||
|
isLoading: authLoading,
|
||||||
|
} = useAuthStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
loginWithTelegram: state.loginWithTelegram,
|
||||||
|
isAuthenticated: state.isAuthenticated,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
})),
|
||||||
|
);
|
||||||
const [status, setStatus] = useState<'loading' | 'success' | 'error' | 'not-telegram'>('loading');
|
const [status, setStatus] = useState<'loading' | 'success' | 'error' | 'not-telegram'>('loading');
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [retryCount, setRetryCount] = useState(() => {
|
const [retryCount, setRetryCount] = useState(() => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSearchParams, Link, useNavigate } from 'react-router';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { authApi } from '../api/auth';
|
import { authApi } from '../api/auth';
|
||||||
import { useAuthStore } from '../store/auth';
|
import { useAuthStore } from '../store/auth';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
import { consumeCampaignSlug } from '../utils/campaign';
|
import { consumeCampaignSlug } from '../utils/campaign';
|
||||||
import { tokenStorage } from '../utils/token';
|
import { tokenStorage } from '../utils/token';
|
||||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||||
@@ -13,7 +14,13 @@ export default function VerifyEmail() {
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const { setTokens, setUser, checkAdminStatus } = useAuthStore();
|
const { setTokens, setUser, checkAdminStatus } = useAuthStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
setTokens: state.setTokens,
|
||||||
|
setUser: state.setUser,
|
||||||
|
checkAdminStatus: state.checkAdminStatus,
|
||||||
|
})),
|
||||||
|
);
|
||||||
const hasVerified = useRef(false);
|
const hasVerified = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export type { WSMessage } from './WebSocketContext';
|
|||||||
const isDev = import.meta.env.DEV;
|
const isDev = import.meta.env.DEV;
|
||||||
|
|
||||||
export function WebSocketProvider({ children }: { children: React.ReactNode }) {
|
export function WebSocketProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { accessToken, isAuthenticated } = useAuthStore();
|
const accessToken = useAuthStore((state) => state.accessToken);
|
||||||
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const pingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const pingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user