refactor: remove socket context dependencies and streamline socket handling in components for improved performance and readability

This commit is contained in:
sebseb7
2025-07-23 07:57:13 +02:00
parent bde001c39b
commit 9982527f35
8 changed files with 99 additions and 186 deletions

View File

@@ -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 (
<Paper

View File

@@ -224,19 +224,6 @@ class Content extends Component {
this.fetchSearchData(this.props.searchParams?.get('q'));
})
}
// 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.loaded) {
// Socket just connected and we haven't loaded data yet, retry loading
if (this.props.params.categoryId) {
this.fetchCategoryData(this.props.params.categoryId);
} else if (this.props.searchParams?.get('q')) {
this.fetchSearchData(this.props.searchParams?.get('q'));
}
}
}
processData(response) {
@@ -278,19 +265,13 @@ class Content extends Component {
return;
}
//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 category data");
// return;
//}
console.log(`productList:${categoryId}`);
this.props.socket.off(`productList:${categoryId}`);
window.socketManager.off(`productList:${categoryId}`);
// Track if we've received the full response to ignore stub response if needed
let receivedFullResponse = false;
this.props.socket.on(`productList:${categoryId}`,(response) => {
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 {
<Box>
<ProductList
socket={this.props.socket}
socketB={this.props.socketB}
totalProductCount={(this.state.unfilteredProducts || []).length}
products={this.state.filteredProducts || []}
activeAttributeFilters={this.state.activeAttributeFilters || []}

View File

@@ -12,7 +12,7 @@ import LoupeIcon from '@mui/icons-material/Loupe';
class Images extends Component {
constructor(props) {
super(props);
this.state = { mainPic:0,pics:[], needsSocketRetry: false };
this.state = { mainPic:0,pics:[] };
}
componentDidMount () {
@@ -22,15 +22,6 @@ class Images extends Component {
if (prevProps.fullscreenOpen !== this.props.fullscreenOpen) {
this.updatePics();
}
// Retry loading images 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.needsSocketRetry) {
this.setState({ needsSocketRetry: false });
this.updatePics();
}
}
updatePics = (newMainPic = this.state.mainPic) => {
@@ -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 (
<>
<Box sx={{ position: 'relative', display: 'inline-block', width: '499px', maxWidth: '100%' }}>
<CardMedia
component="img"
height="400"
image={getImagePath(this.props.pictureList)}
alt={this.props.productName || 'Produktbild'}
fetchPriority="high"
loading="eager"
sx={{
objectFit: 'contain',
cursor: 'pointer',
transition: 'transform 0.2s ease-in-out',
width: '499px',
maxWidth: '100%',
'&:hover': {
transform: 'scale(1.02)'
}
}}
/>
</Box>
<Stack direction="row" spacing={2} sx={{ justifyContent: 'flex-start', mt: 1, mb: 1 }}>
{/* Empty thumbnail gallery for prerender - reserves the mt+mb spacing (16px) */}
</Stack>
</>
);
}
// SPA version - full functionality with static fallback
const getImageSrc = () => {
// If dynamic image is loaded, use it

View File

@@ -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;

View File

@@ -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 {
>
<ProductImage
product={product}
socket={this.props.socket}
socketB={this.props.socketB}
fullscreenOpen={this.state.imageDialogOpen}
onOpenFullscreen={this.handleOpenDialog}
onCloseFullscreen={this.handleCloseDialog}
@@ -1032,7 +1004,6 @@ class ProductDetailPage extends Component {
<ArticleQuestionForm
productId={product.id}
productName={cleanProductName(product.name)}
socket={this.props.socket}
/>
</div>
</Collapse>
@@ -1043,7 +1014,6 @@ class ProductDetailPage extends Component {
<ArticleRatingForm
productId={product.id}
productName={cleanProductName(product.name)}
socket={this.props.socket}
/>
</div>
</Collapse>
@@ -1054,7 +1024,6 @@ class ProductDetailPage extends Component {
<ArticleAvailabilityForm
productId={product.id}
productName={cleanProductName(product.name)}
socket={this.props.socket}
/>
</Collapse>
)}

View File

@@ -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 (
<SocketContext.Consumer>
{({socket,socketB}) => <ProductDetailPage seoName={seoName} navigate={navigate} location={location} socket={socket} socketB={socketB} />}
</SocketContext.Consumer>
<ProductDetailPage seoName={seoName} navigate={navigate} location={location} />
);
};

View File

@@ -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(

View File

@@ -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