From 9982527f357cd43c9d61a5b1a35ee6e15dd54aaa Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Wed, 23 Jul 2025 07:57:13 +0200 Subject: [PATCH] refactor: remove socket context dependencies and streamline socket handling in components for improved performance and readability --- src/components/CategoryBox.js | 15 ++-- src/components/Content.js | 36 ++-------- src/components/Images.js | 58 +--------------- src/components/Product.js | 32 ++------- src/components/ProductDetailPage.js | 43 ++---------- src/components/ProductDetailWithSocket.js | 6 +- src/components/SharedCarousel.js | 10 +-- src/services/SocketManager.js | 85 +++++++++++++++++++---- 8 files changed, 99 insertions(+), 186 deletions(-) diff --git a/src/components/CategoryBox.js b/src/components/CategoryBox.js index 4b9bc7c..1fe8c39 100644 --- a/src/components/CategoryBox.js +++ b/src/components/CategoryBox.js @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect } from 'react'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; import { Link } from 'react-router-dom'; -import SocketContext from '../contexts/SocketContext.js'; + // @note SwashingtonCP font is now loaded globally via index.css @@ -22,7 +22,7 @@ const CategoryBox = ({ const [imageUrl, setImageUrl] = useState(null); const [imageError, setImageError] = useState(false); const [isLoading, setIsLoading] = useState(false); - const context = useContext(SocketContext); + useEffect(() => { let objectUrl = null; @@ -60,11 +60,10 @@ const CategoryBox = ({ return; } - // If socket is available and connected, fetch the image - if (context && context.socket /*&& context.socket.connected*/ && id && !isLoading) { + if (id && !isLoading) { setIsLoading(true); - - context.socket.emit('getCategoryPic', { categoryId: id }, (response) => { + + window.socketManager.emit('getCategoryPic', { categoryId: id }, (response) => { setIsLoading(false); if (response.success) { @@ -119,7 +118,7 @@ const CategoryBox = ({ URL.revokeObjectURL(objectUrl); } }; - }, [context, context?.socket?.connected, id, isLoading]); + }, [id, isLoading]); return ( { + window.socketManager.on(`productList:${categoryId}`,(response) => { console.log("getCategoryProducts full response", response); receivedFullResponse = true; setCachedCategoryData(categoryId, response); @@ -301,7 +282,7 @@ class Content extends Component { } }); - this.props.socket.emit("getCategoryProducts", { categoryId: categoryId }, + window.socketManager.emit("getCategoryProducts", { categoryId: categoryId }, (response) => { console.log("getCategoryProducts stub response", response); // Only process stub response if we haven't received the full response yet @@ -365,14 +346,7 @@ class Content extends Component { } fetchSearchData(query) { - // if (!this.props.socket || !this.props.socket.connected) { - // Socket not connected yet, but don't show error immediately on first load - // The componentDidUpdate will retry when socket connects - // console.log("Socket not connected yet, waiting for connection to fetch search data"); - // return; - // } - - this.props.socket.emit("getSearchProducts", { query }, + window.socketManager.emit("getSearchProducts", { query }, (response) => { if (response && response.products) { this.processData(response); @@ -734,8 +708,6 @@ class Content extends Component { { @@ -85,14 +76,9 @@ class Images extends Component { } loadPic = (size,bildId,index) => { - // Check if socketB is available and connected before emitting - if (!this.props.socketB || !this.props.socketB.connected) { - console.log("Images: socketB not available, will retry when connected"); - this.setState({ needsSocketRetry: true }); - return; - } + - this.props.socketB.emit('getPic', { bildId, size }, (res) => { + window.socketManager.emit('getPic', { bildId, size }, (res) => { if(res.success){ const url = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' })); @@ -116,44 +102,6 @@ class Images extends Component { } render() { - // Prerender detection - if no sockets, render simple CardMedia with static path - if (!this.props.socketB) { - const getImagePath = (pictureList) => { - if (!pictureList || !pictureList.trim()) { - return '/assets/images/nopicture.jpg'; - } - return `/assets/images/prod${pictureList.split(',')[0].trim()}.jpg`; - }; - - return ( - <> - - - - - {/* Empty thumbnail gallery for prerender - reserves the mt+mb spacing (16px) */} - - - ); - } - // SPA version - full functionality with static fallback const getImageSrc = () => { // If dynamic image is loaded, use it diff --git a/src/components/Product.js b/src/components/Product.js index fb52f59..102cc41 100644 --- a/src/components/Product.js +++ b/src/components/Product.js @@ -27,17 +27,8 @@ class Product extends Component { this.state = {image:window.smallPicCache[bildId],loading:false, error: false} }else{ this.state = {image: null, loading: true, error: false}; - console.log("Product: Fetching image from socketB", this.props.socketB); - // Check if socketB is available and connected before emitting - if (this.props.socketB && this.props.socketB.connected) { - this.loadImage(bildId); - } else { - // Socket not available, set error state or wait - console.log("Product: socketB not available, will retry when connected"); - this.state.error = true; - this.state.loading = false; - } + this.loadImage(bildId); } }else{ this.state = {image: null, loading: false, error: false}; @@ -48,24 +39,9 @@ class Product extends Component { this._isMounted = true; } - componentDidUpdate(prevProps) { - // Retry loading image if socket just became available - const wasConnected = prevProps.socketB && prevProps.socketB.connected; - const isNowConnected = this.props.socketB && this.props.socketB.connected; - - if (!wasConnected && isNowConnected && this.state.error && this.props.pictureList) { - // Socket just connected and we had an error, retry loading - const bildId = this.props.pictureList.split(',')[0]; - if (!window.smallPicCache[bildId]) { - this.setState({loading: true, error: false}); - this.loadImage(bildId); - } - } - } - loadImage = (bildId) => { - if (this.props.socketB && this.props.socketB.connected) { - this.props.socketB.emit('getPic', { bildId, size:'small' }, (res) => { + + window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => { if(res.success){ window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' })); if (this._isMounted) { @@ -85,7 +61,7 @@ class Product extends Component { } }); } - } + componentWillUnmount() { this._isMounted = false; diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index d4b6272..989e4d5 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -105,15 +105,6 @@ class ProductDetailPage extends Component { { product: null, loading: true, error: null, imageDialogOpen: false }, this.loadProductData ); - - // Handle socket connection changes - const wasConnected = prevProps.socket && prevProps.socket.connected; - const isNowConnected = this.props.socket && this.props.socket.connected; - - if (!wasConnected && isNowConnected && this.state.loading) { - // Socket just connected and we're still loading, retry loading data - this.loadProductData(); - } } loadKomponentImage = (komponentId, pictureList) => { @@ -141,14 +132,8 @@ class ProductDetailPage extends Component { return; } - // Check if socketB is available - if (!this.props.socketB || !this.props.socketB.connected) { - console.log("SocketB not connected yet, skipping image load for komponent:", komponentId); - return; - } - // Fetch image from server - this.props.socketB.emit('getPic', { bildId, size: 'small' }, (res) => { + window.socketManager.emit('getPic', { bildId, size: 'small' }, (res) => { if (res.success) { // Cache the image window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' })); @@ -229,12 +214,6 @@ class ProductDetailPage extends Component { return; } - // If not cached, fetch from server (similar to loadProductData) - //if (!this.props.socket || !this.props.socket.connected) { - // console.log("Socket not connected yet, waiting for connection to load komponent data"); - // return; - //} - // Mark this komponent as loading this.setState(prevState => ({ komponentenData: { @@ -248,7 +227,7 @@ class ProductDetailPage extends Component { } })); - this.props.socket.emit( + window.socketManager.emit( "getProductView", { articleId: id }, (res) => { @@ -359,13 +338,8 @@ class ProductDetailPage extends Component { } loadProductData = () => { - if (!this.props.socket || !this.props.socket.connected) { - // Socket not connected yet, but don't show error immediately on first load - // The componentDidUpdate will retry when socket connects - return; - } - this.props.socket.emit( + window.socketManager.emit( "getProductView", { seoName: this.props.seoName }, (res) => { @@ -434,8 +408,8 @@ class ProductDetailPage extends Component { } } else { // Not in cache, fetch from server - if (this.props.socketB && this.props.socketB.connected) { - this.props.socketB.emit( + + window.socketManager.emit( "getAttributePicture", { id: cacheKey }, (res) => { @@ -469,7 +443,7 @@ class ProductDetailPage extends Component { } } ); - } + } } @@ -698,8 +672,6 @@ class ProductDetailPage extends Component { > @@ -1043,7 +1014,6 @@ class ProductDetailPage extends Component { @@ -1054,7 +1024,6 @@ class ProductDetailPage extends Component { )} diff --git a/src/components/ProductDetailWithSocket.js b/src/components/ProductDetailWithSocket.js index 60008cc..b5fad1e 100644 --- a/src/components/ProductDetailWithSocket.js +++ b/src/components/ProductDetailWithSocket.js @@ -1,18 +1,14 @@ import React from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; -import SocketContext from '../contexts/SocketContext.js'; import ProductDetailPage from './ProductDetailPage.js'; -// Wrapper component for individual product detail page with socket const ProductDetailWithSocket = () => { const { seoName } = useParams(); const navigate = useNavigate(); const location = useLocation(); return ( - - {({socket,socketB}) => } - + ); }; diff --git a/src/components/SharedCarousel.js b/src/components/SharedCarousel.js index ad7b616..c2b6361 100644 --- a/src/components/SharedCarousel.js +++ b/src/components/SharedCarousel.js @@ -1,11 +1,10 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; import ChevronLeft from "@mui/icons-material/ChevronLeft"; import ChevronRight from "@mui/icons-material/ChevronRight"; import CategoryBox from "./CategoryBox.js"; -import SocketContext from "../contexts/SocketContext.js"; import { useCarousel } from "../contexts/CarouselContext.js"; import { useTranslation } from 'react-i18next'; @@ -62,7 +61,6 @@ const initializeCategories = (language = 'en') => { const SharedCarousel = () => { const { carouselRef, filteredCategories, setFilteredCategories, moveCarousel } = useCarousel(); - const context = useContext(SocketContext); const { t, i18n } = useTranslation(); const [rootCategories, setRootCategories] = useState([]); const [currentLanguage, setCurrentLanguage] = useState(i18n.language || 'de'); @@ -94,13 +92,11 @@ const SharedCarousel = () => { }, [i18n]); useEffect(() => { - // Only fetch from socket if we don't already have categories if ( rootCategories.length === 0 && - context && context.socket && context.socket.connected && typeof window !== "undefined" ) { - context.socket.emit("categoryList", { categoryId: 209, language: currentLanguage, requestTranslation: true }, (response) => { + window.socketManager.emit("categoryList", { categoryId: 209, language: currentLanguage, requestTranslation: true }, (response) => { if (response && response.success) { // Use translated data if available, otherwise fall back to original const categoryTreeToUse = response.translation || response.categoryTree; @@ -136,7 +132,7 @@ const SharedCarousel = () => { } }); } - }, [context, context?.socket?.connected, rootCategories.length, currentLanguage]); + }, [rootCategories.length, currentLanguage]); useEffect(() => { const filtered = rootCategories.filter( diff --git a/src/services/SocketManager.js b/src/services/SocketManager.js index 673b3b9..70ad7fb 100644 --- a/src/services/SocketManager.js +++ b/src/services/SocketManager.js @@ -3,32 +3,90 @@ import { io } from 'socket.io-client'; class SocketManager { constructor() { - this.socket = io('', { transports: ["websocket"], autoConnect: false }); this.emit = this.emit.bind(this); + this.on = this.on.bind(this); + this.off = this.off.bind(this); + this.connectPromise = null; + this.pendingListeners = new Map(); + } + + on(event, callback) { + // If socket is already connected, register the listener directly + if (this.socket.connected) { + this.socket.on(event, callback); + return; + } + + // Store the listener to be registered when connection is established + if (!this.pendingListeners.has(event)) { + this.pendingListeners.set(event, new Set()); + } + this.pendingListeners.get(event).add(callback); + + // If not already connecting, initiate connection + if (!this.connectPromise) { + this.connect(); + } + + // Register the listener now, it will receive events once connected + this.socket.on(event, callback); + } + + off(event, callback) { + // Remove from socket listeners + this.socket.off(event, callback); + + // Remove from pending listeners if present + if (this.pendingListeners.has(event)) { + const listeners = this.pendingListeners.get(event); + listeners.delete(callback); + if (listeners.size === 0) { + this.pendingListeners.delete(event); + } + } + } + + connect() { + if (this.connectPromise) return this.connectPromise; + + this.connectPromise = new Promise((resolve, reject) => { + this.socket.connect(); + + this.socket.once('connect', () => { + resolve(); + }); + + this.socket.once('connect_error', (error) => { + this.connectPromise = null; + reject(error); + }); + }); + + return this.connectPromise; } - emit(event, ...args) { return new Promise((resolve, reject) => { if (!this.socket.connected) { - // Connect the socket first - this.socket.connect(); + // If not already connecting, start connection + if (!this.connectPromise) { + this.connect(); + } // Wait for connection before emitting - this.socket.once('connect', () => { - this.socket.emit(event, ...args); - resolve(); - }); - - // Handle connection error - this.socket.once('connect_error', (error) => { - reject(error); - }); + this.connectPromise + .then(() => { + this.socket.emit(event, ...args); + resolve(); + }) + .catch((error) => { + reject(error); + }); } else { // Socket already connected, emit directly this.socket.emit(event, ...args); @@ -36,7 +94,6 @@ class SocketManager { } }); } - } // Create singleton instance