Skip to content

Commit 9f534f3

Browse files
authored
Merge pull request #804 from Sim-sat/lint-effects
fix(lint): fix react-hooks lint errors
2 parents 3e23bee + cb70557 commit 9f534f3

16 files changed

Lines changed: 612 additions & 584 deletions

SparkyFitnessFrontend/src/components/DraggableChatbotButton.tsx

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,33 @@ interface ButtonState {
2323

2424
const DraggableChatbotButton: React.FC = () => {
2525
const { toggleChat, isChatOpen } = useChatbotVisibility();
26-
const { user, loading } = useAuth();
27-
const [buttonState, setButtonState] = useState<ButtonState>({
28-
y: 0,
29-
edge: 'right',
30-
minimized: false,
26+
const { user } = useAuth();
27+
const [buttonState, setButtonState] = useState<ButtonState>(() => {
28+
const bottomOffset = window.innerWidth < 768 ? BOTTOM_NAV_HEIGHT : 0; // Quick check based on typical mobile breakpoint
29+
const defaultY =
30+
window.innerHeight - BUTTON_SIZE - EDGE_MARGIN - bottomOffset;
31+
32+
const defaultState: ButtonState = {
33+
y: defaultY,
34+
edge: 'right',
35+
minimized: false,
36+
};
37+
38+
const saved = localStorage.getItem(STORAGE_KEY);
39+
if (saved) {
40+
try {
41+
const parsed = JSON.parse(saved) as ButtonState;
42+
const maxY = window.innerHeight - BUTTON_SIZE - EDGE_MARGIN;
43+
return {
44+
...parsed,
45+
y: Math.min(Math.max(EDGE_MARGIN, parsed.y), maxY),
46+
};
47+
} catch {
48+
return defaultState;
49+
}
50+
}
51+
52+
return defaultState;
3153
});
3254
const [isDragging, setIsDragging] = useState(false);
3355
const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 });
@@ -38,8 +60,7 @@ const DraggableChatbotButton: React.FC = () => {
3860
const dragOffset = useRef({ x: 0, y: 0 });
3961
const containerRef = useRef<HTMLDivElement>(null);
4062
const isMobile = useIsMobile();
41-
const { data: activeService, isLoading: serviceLoading } =
42-
useActiveAIService(!!user);
63+
const { data: activeService } = useActiveAIService(!!user);
4364

4465
// Berechneter Wert statt State
4566
const hasAiProvider = !!(
@@ -61,36 +82,6 @@ const DraggableChatbotButton: React.FC = () => {
6182
};
6283
}, [buttonState, isDragging, dragPosition]);
6384

64-
// Initialize position from localStorage or default to bottom-right
65-
useEffect(() => {
66-
const saved = localStorage.getItem(STORAGE_KEY);
67-
if (saved) {
68-
try {
69-
const parsed = JSON.parse(saved) as ButtonState;
70-
// Ensure y is within bounds
71-
const maxY = window.innerHeight - BUTTON_SIZE - EDGE_MARGIN;
72-
setButtonState({
73-
...parsed,
74-
y: Math.min(Math.max(EDGE_MARGIN, parsed.y), maxY),
75-
});
76-
} catch {
77-
setDefaultPosition();
78-
}
79-
} else {
80-
setDefaultPosition();
81-
}
82-
// eslint-disable-next-line react-hooks/exhaustive-deps
83-
}, []);
84-
85-
const setDefaultPosition = () => {
86-
const bottomOffset = isMobile ? BOTTOM_NAV_HEIGHT : 0;
87-
setButtonState({
88-
y: window.innerHeight - BUTTON_SIZE - EDGE_MARGIN - bottomOffset,
89-
edge: 'right',
90-
minimized: false,
91-
});
92-
};
93-
9485
// Calculate max Y position (respects bottom nav on mobile)
9586
const getMaxY = useCallback(
9687
(minimized: boolean) => {
@@ -199,12 +190,12 @@ const DraggableChatbotButton: React.FC = () => {
199190
}
200191
};
201192

202-
const handleInteractionEnd = () => {
193+
const handleInteractionEnd = useCallback(() => {
203194
if (isDragging && hasDragged.current) {
204195
snapToEdge(dragPosition.x, dragPosition.y);
205196
}
206197
setIsDragging(false);
207-
};
198+
}, [isDragging, hasDragged, snapToEdge, dragPosition, setIsDragging]);
208199

209200
const handleMouseDown = (e: React.MouseEvent) => {
210201
handleInteractionStart(e.clientX, e.clientY);
@@ -220,8 +211,7 @@ const DraggableChatbotButton: React.FC = () => {
220211

221212
const handleMouseUp = useCallback(() => {
222213
handleInteractionEnd();
223-
// eslint-disable-next-line react-hooks/exhaustive-deps
224-
}, [isDragging, hasDragged, dragPosition, snapToEdge]);
214+
}, [handleInteractionEnd]);
225215

226216
const handleTouchStart = useCallback((e: TouchEvent) => {
227217
if (e.touches.length === 1) {
@@ -239,17 +229,7 @@ const DraggableChatbotButton: React.FC = () => {
239229
[updatePosition]
240230
);
241231

242-
const handleTouchEnd = useCallback(() => {
243-
if (isDragging) {
244-
handleInteractionEnd();
245-
if (!hasDragged.current) {
246-
handleClick();
247-
}
248-
}
249-
// eslint-disable-next-line react-hooks/exhaustive-deps
250-
}, [isDragging, hasDragged, dragPosition, snapToEdge]);
251-
252-
const handleClick = () => {
232+
const handleClick = useCallback(() => {
253233
if (!hasDragged.current) {
254234
if (buttonState.minimized) {
255235
// Restore from minimized with animation
@@ -264,7 +244,15 @@ const DraggableChatbotButton: React.FC = () => {
264244
}
265245
}
266246
hasDragged.current = false;
267-
};
247+
}, [setIsRestoring, setButtonState, toggleChat, buttonState]);
248+
const handleTouchEnd = useCallback(() => {
249+
if (isDragging) {
250+
handleInteractionEnd();
251+
if (!hasDragged.current) {
252+
handleClick();
253+
}
254+
}
255+
}, [isDragging, hasDragged, handleClick, handleInteractionEnd]);
268256

269257
const handleMinimize = (e: React.MouseEvent) => {
270258
e.stopPropagation();

SparkyFitnessFrontend/src/components/FoodSearch/BarcodeScanner.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ const BarcodeScanner: React.FC<BarcodeScannerProps> = ({
139139
if (isActive) {
140140
getCameras();
141141
}
142-
// eslint-disable-next-line react-hooks/exhaustive-deps
143-
}, [isActive, cameraFacing]); // Re-check when active toggles
142+
}, [isActive, cameraFacing, selectedCameraId]); // Re-check when active toggles
144143

145144
const turnOffTorch = useCallback(async () => {
146145
if (!currentTrack.current || !torchSupported || !torchEnabled) return;

SparkyFitnessFrontend/src/components/FoodSearch/CustomFoodForm.tsx

Lines changed: 82 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useCallback } from 'react';
22
import { Button } from '@/components/ui/button';
33
import { Checkbox } from '@/components/ui/checkbox';
44
import { Input } from '@/components/ui/input';
@@ -214,87 +214,7 @@ const EnhancedCustomFoodForm = ({
214214
is_quick_food: false,
215215
});
216216

217-
useEffect(() => {
218-
if (food) {
219-
setFormData({
220-
name: food.name || '',
221-
brand: food.brand || '',
222-
is_quick_food: food.is_quick_food || false,
223-
});
224-
if (food.variants && food.variants.length > 0) {
225-
const mappedVariants = food.variants.map((v) =>
226-
foodVariantToFormVariant({
227-
...v,
228-
is_locked: false,
229-
glycemic_index: sanitizeGlycemicIndexFrontend(v.glycemic_index),
230-
})
231-
);
232-
setVariants(mappedVariants);
233-
setOriginalVariants(JSON.parse(JSON.stringify(mappedVariants))); // Deep copy for original values
234-
setVariantErrors(new Array(food.variants.length).fill(null)); // Initialize errors for existing variants
235-
} else {
236-
loadExistingVariants();
237-
}
238-
} else if (initialVariants && initialVariants.length > 0) {
239-
setFormData({
240-
name: '',
241-
brand: '',
242-
is_quick_food: false,
243-
});
244-
const mappedInitialVariants = initialVariants.map(
245-
foodVariantToFormVariant
246-
);
247-
setVariants(mappedInitialVariants);
248-
setOriginalVariants(JSON.parse(JSON.stringify(mappedInitialVariants))); // Deep copy for original values
249-
setVariantErrors(new Array(initialVariants.length).fill(null)); // Initialize errors for initial variants
250-
} else {
251-
setFormData({
252-
name: '',
253-
brand: '',
254-
is_quick_food: false,
255-
});
256-
const defaultVariant: FormFoodVariant = {
257-
serving_size: 100,
258-
serving_unit: 'g',
259-
calories: '',
260-
protein: '',
261-
carbs: '',
262-
fat: '',
263-
saturated_fat: '',
264-
polyunsaturated_fat: '',
265-
monounsaturated_fat: '',
266-
trans_fat: '',
267-
cholesterol: '',
268-
sodium: '',
269-
potassium: '',
270-
dietary_fiber: '',
271-
sugars: '',
272-
vitamin_a: '',
273-
vitamin_c: '',
274-
calcium: '',
275-
iron: '',
276-
is_default: true,
277-
is_locked: false,
278-
glycemic_index: 'None' as GlycemicIndex,
279-
custom_nutrients: {},
280-
};
281-
282-
if (customNutrients && customNutrients.length > 0) {
283-
customNutrients.forEach((nutrient) => {
284-
if (defaultVariant.custom_nutrients) {
285-
defaultVariant.custom_nutrients[nutrient.name] = '';
286-
}
287-
});
288-
}
289-
290-
setVariants([defaultVariant]);
291-
setOriginalVariants([JSON.parse(JSON.stringify(defaultVariant))]); // Deep copy
292-
setVariantErrors([null]); // Initialize error for the single default variant
293-
}
294-
// eslint-disable-next-line react-hooks/exhaustive-deps
295-
}, [food, initialVariants, customNutrients]);
296-
297-
const loadExistingVariants = async () => {
217+
const loadExistingVariants = useCallback(async () => {
298218
if (!food?.id || !isUUID(food.id)) return; // Ensure food.id is a valid UUID
299219

300220
try {
@@ -396,7 +316,86 @@ const EnhancedCustomFoodForm = ({
396316
setVariants([defaultVariant]);
397317
setOriginalVariants([JSON.parse(JSON.stringify(defaultVariant))]); // Deep copy for original values
398318
}
399-
};
319+
}, [food.default_variant, food.id, queryClient]);
320+
321+
useEffect(() => {
322+
if (food) {
323+
setFormData({
324+
name: food.name || '',
325+
brand: food.brand || '',
326+
is_quick_food: food.is_quick_food || false,
327+
});
328+
if (food.variants && food.variants.length > 0) {
329+
const mappedVariants = food.variants.map((v) =>
330+
foodVariantToFormVariant({
331+
...v,
332+
is_locked: false,
333+
glycemic_index: sanitizeGlycemicIndexFrontend(v.glycemic_index),
334+
})
335+
);
336+
setVariants(mappedVariants);
337+
setOriginalVariants(JSON.parse(JSON.stringify(mappedVariants))); // Deep copy for original values
338+
setVariantErrors(new Array(food.variants.length).fill(null)); // Initialize errors for existing variants
339+
} else {
340+
loadExistingVariants();
341+
}
342+
} else if (initialVariants && initialVariants.length > 0) {
343+
setFormData({
344+
name: '',
345+
brand: '',
346+
is_quick_food: false,
347+
});
348+
const mappedInitialVariants = initialVariants.map(
349+
foodVariantToFormVariant
350+
);
351+
setVariants(mappedInitialVariants);
352+
setOriginalVariants(JSON.parse(JSON.stringify(mappedInitialVariants))); // Deep copy for original values
353+
setVariantErrors(new Array(initialVariants.length).fill(null)); // Initialize errors for initial variants
354+
} else {
355+
setFormData({
356+
name: '',
357+
brand: '',
358+
is_quick_food: false,
359+
});
360+
const defaultVariant: FormFoodVariant = {
361+
serving_size: 100,
362+
serving_unit: 'g',
363+
calories: '',
364+
protein: '',
365+
carbs: '',
366+
fat: '',
367+
saturated_fat: '',
368+
polyunsaturated_fat: '',
369+
monounsaturated_fat: '',
370+
trans_fat: '',
371+
cholesterol: '',
372+
sodium: '',
373+
potassium: '',
374+
dietary_fiber: '',
375+
sugars: '',
376+
vitamin_a: '',
377+
vitamin_c: '',
378+
calcium: '',
379+
iron: '',
380+
is_default: true,
381+
is_locked: false,
382+
glycemic_index: 'None' as GlycemicIndex,
383+
custom_nutrients: {},
384+
};
385+
386+
if (customNutrients && customNutrients.length > 0) {
387+
customNutrients.forEach((nutrient) => {
388+
if (defaultVariant.custom_nutrients) {
389+
defaultVariant.custom_nutrients[nutrient.name] = '';
390+
}
391+
});
392+
}
393+
394+
setVariants([defaultVariant]);
395+
setOriginalVariants([JSON.parse(JSON.stringify(defaultVariant))]); // Deep copy
396+
setVariantErrors([null]); // Initialize error for the single default variant
397+
}
398+
}, [food, initialVariants, customNutrients, loadExistingVariants]);
400399

401400
const addVariant = () => {
402401
const newVariant: FormFoodVariant = {

0 commit comments

Comments
 (0)