Skip to content

Commit 47101a8

Browse files
committed
fix: fix useVisualisation hook
1 parent 345cfff commit 47101a8

1 file changed

Lines changed: 48 additions & 64 deletions

File tree

src/hooks/useVisualization.js

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@ import {
55
DEFAULT_ARRAY_SIZE,
66
} from '../constants';
77

8-
/**
9-
* Custom hook for managing visualization state and animation playback
10-
* @param {number[]} initialArray - The initial array to visualize
11-
* @param {number} speed - Animation base speed in milliseconds (from ANIMATION_SPEEDS)
12-
* @param {string} mode - Visualization mode ('autoplay' or 'manual')
13-
* @returns {Object} Visualization state and controls
14-
*/
158
export function useVisualization(
169
initialArray,
1710
speed,
@@ -32,23 +25,28 @@ export function useVisualization(
3225
const stepsRef = useRef([]);
3326
const autoplayTimeoutRef = useRef(null);
3427

35-
// keep stepsRef in sync
28+
// Centralized cleanup function
29+
const clearAutoplayTimeout = useCallback(() => {
30+
if (autoplayTimeoutRef.current) {
31+
clearTimeout(autoplayTimeoutRef.current);
32+
autoplayTimeoutRef.current = null;
33+
}
34+
}, []);
35+
36+
// Keep stepsRef in sync
3637
useEffect(() => {
3738
stepsRef.current = steps;
3839
}, [steps]);
3940

40-
// cleanup on unmount: clear any pending timeout
41+
// Cleanup on unmount
4142
useEffect(() => {
4243
return () => {
43-
if (autoplayTimeoutRef.current) {
44-
clearTimeout(autoplayTimeoutRef.current);
45-
autoplayTimeoutRef.current = null;
46-
}
44+
clearAutoplayTimeout();
4745
animationRef.current = null;
4846
};
49-
}, []);
47+
}, [clearAutoplayTimeout]);
5048

51-
// Reset when array changes (external array update)
49+
// Reset when array changes
5250
useEffect(() => {
5351
setArray(initialArray);
5452
setStates(Array(initialArray.length).fill(ELEMENT_STATES.DEFAULT));
@@ -57,41 +55,35 @@ export function useVisualization(
5755
setIsComplete(false);
5856
setIsPlaying(false);
5957
setIsAutoplayActive(false);
60-
if (autoplayTimeoutRef.current) {
61-
clearTimeout(autoplayTimeoutRef.current);
62-
autoplayTimeoutRef.current = null;
63-
}
64-
}, [initialArray]);
65-
66-
const loadSteps = useCallback(algorithmSteps => {
67-
setSteps(algorithmSteps);
68-
setCurrentStep(0);
69-
setIsComplete(false);
58+
clearAutoplayTimeout();
59+
}, [initialArray, clearAutoplayTimeout]);
60+
61+
const loadSteps = useCallback(
62+
algorithmSteps => {
63+
clearAutoplayTimeout();
64+
setSteps(algorithmSteps);
65+
setCurrentStep(0);
66+
setIsComplete(false);
7067

71-
if (algorithmSteps.length > 0) {
72-
const firstStep = algorithmSteps[0];
73-
setArray(firstStep.array);
74-
setStates(firstStep.states);
75-
setDescription(firstStep.description);
76-
}
77-
}, []);
68+
if (algorithmSteps.length > 0) {
69+
const firstStep = algorithmSteps[0];
70+
setArray(firstStep.array);
71+
setStates(firstStep.states);
72+
setDescription(firstStep.description);
73+
}
74+
},
75+
[clearAutoplayTimeout]
76+
);
7877

79-
/**
80-
* computeEffectiveDelay
81-
* - scales baseDelay inversely with array length so big arrays don't take forever
82-
* - caps total run time to MAX_TOTAL_MS when possible
83-
*/
8478
const computeEffectiveDelay = (baseDelay, arrayLength, totalSteps) => {
85-
const MAX_TOTAL_MS = 30000; // max total autoplay time default (30s)
86-
const MIN_DELAY_MS = 30; // lower bound per step
87-
const MAX_DELAY_MS = 10000; // upper bound per step
79+
const MAX_TOTAL_MS = 30000;
80+
const MIN_DELAY_MS = 30;
81+
const MAX_DELAY_MS = 10000;
8882

89-
// scale delay by default array size ratio
9083
const scaled = Math.round(
9184
baseDelay * (DEFAULT_ARRAY_SIZE / Math.max(1, arrayLength))
9285
);
9386

94-
// enforce a cap so total time isn't ridiculous
9587
let final = scaled;
9688
if (totalSteps && final * totalSteps > MAX_TOTAL_MS) {
9789
final = Math.max(MIN_DELAY_MS, Math.floor(MAX_TOTAL_MS / totalSteps));
@@ -100,14 +92,10 @@ export function useVisualization(
10092
return Math.max(MIN_DELAY_MS, Math.min(final, MAX_DELAY_MS));
10193
};
10294

103-
/**
104-
* Starts autoplay animation or performs single step in manual mode
105-
*/
10695
const play = useCallback(() => {
10796
if (stepsRef.current.length === 0 || isComplete) return;
10897

10998
if (mode === VISUALIZATION_MODES.MANUAL) {
110-
// Manual: advance a single step
11199
if (currentStep < stepsRef.current.length - 1) {
112100
const nextStep = currentStep + 1;
113101
const step = stepsRef.current[nextStep];
@@ -124,6 +112,7 @@ export function useVisualization(
124112
}
125113

126114
// Autoplay
115+
clearAutoplayTimeout();
127116
setIsPlaying(true);
128117
setIsAutoplayActive(true);
129118
animationRef.current = true;
@@ -132,6 +121,7 @@ export function useVisualization(
132121
if (!animationRef.current || stepIndex >= stepsRef.current.length) {
133122
setIsPlaying(false);
134123
setIsAutoplayActive(false);
124+
clearAutoplayTimeout();
135125
if (stepIndex >= stepsRef.current.length) {
136126
setIsComplete(true);
137127
}
@@ -148,6 +138,7 @@ export function useVisualization(
148138
setIsComplete(true);
149139
setIsPlaying(false);
150140
setIsAutoplayActive(false);
141+
clearAutoplayTimeout();
151142
return;
152143
}
153144

@@ -156,36 +147,29 @@ export function useVisualization(
156147
const totalSteps = stepsRef.current.length || 0;
157148
const effectiveDelay = computeEffectiveDelay(speed, arrayLen, totalSteps);
158149

159-
// clear any existing timeout before scheduling next
160-
if (autoplayTimeoutRef.current) {
161-
clearTimeout(autoplayTimeoutRef.current);
162-
autoplayTimeoutRef.current = null;
163-
}
164-
150+
clearAutoplayTimeout();
165151
autoplayTimeoutRef.current = setTimeout(() => {
166152
runAutoplay(stepIndex + 1);
167153
}, effectiveDelay);
168154
};
169155

170156
runAutoplay(currentStep);
171-
}, [currentStep, speed, isComplete, mode, array.length]);
157+
}, [
158+
currentStep,
159+
speed,
160+
isComplete,
161+
mode,
162+
array.length,
163+
clearAutoplayTimeout,
164+
]);
172165

173-
/**
174-
* Pauses autoplay (can be resumed)
175-
*/
176166
const pause = useCallback(() => {
177167
animationRef.current = null;
178168
setIsPlaying(false);
179169
setIsAutoplayActive(false);
180-
if (autoplayTimeoutRef.current) {
181-
clearTimeout(autoplayTimeoutRef.current);
182-
autoplayTimeoutRef.current = null;
183-
}
184-
}, []);
170+
clearAutoplayTimeout();
171+
}, [clearAutoplayTimeout]);
185172

186-
/**
187-
* Resets to initial step
188-
*/
189173
const reset = useCallback(() => {
190174
pause();
191175
setCurrentStep(0);

0 commit comments

Comments
 (0)