I am trying to build a sorting algorithm visualizer that sorts an element of an array containing integers once every second upon clicking a button. Whenever I click the "selection sort" button, however, it dispatches the "randomize" action type first, then "selection sort." This would be fine if it still sorted the array, but it doesn’t, instead the "selection sort" button functionally acts as a second "randomize" button. Am I using the useReducer hook wrong? How can I make it so that only one action type is dispatched?
arr-context-provider.tsx
import { createContext, useReducer } from "react";
import ControlPanel from './components/control-panel';
import Visualizer from './components/visualizer';
interface ArrayContextProps {
children: React.ReactElement;
}
export type Action =
| { type: "selection sort" }
| { type: "randomize" }
const initArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
export const arrContext = createContext<[number[], React.Dispatch<Action>]>([initArr, () => initArr]);
const ArrContextProvider: React.FC<ArrayContextProps> = ({ children }: ArrayContextProps) => {
const arrReducer = (arr: number[], action: Action) => {
switch (action.type) {
case "selection sort":
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
let minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
console.log("selection sort!");
return [...arr.slice()];
}, 100 * (i + 1));
}
case "randomize":
arr = [];
while (arr.length < 10) {
let rng = Math.floor(Math.random() * 10) + 1;
if (arr.indexOf(rng) === -1) {
arr.push(rng);
}
}
console.log("randomize!");
return [...arr.slice()];
default:
console.log("error!");
return [...arr.slice()];
}
}
const [arr, dispatch] = useReducer<(arr: number[], action: Action) => number[]>(arrReducer, initArr);
return (
<arrContext.Provider value={[arr, dispatch]}>
<div>
<ControlPanel />
<Visualizer />
</div>
</arrContext.Provider>
);
};
export default ArrContextProvider;
control-panel.tsx
import { useState, useEffect, useContext } from 'react';
import { arrContext, Action } from './arr-context-provider';
const ControlPanel: React.FC = () => {
const [arr, dispatch] = useContext<[number[], React.Dispatch<Action>]>(arrContext);
const [algorithm, setAlgorithm] = useState("");
useEffect(() => {
switch(algorithm) {
case "selection sort":
dispatch({ type: "selection sort" });
case "randomize":
dispatch({ type: "randomize" });
setAlgorithm("");
default:
[...arr.slice()];
}
}, [algorithm]);
return (
<div className='headerContainer'>
<h1 className='header'>ππ
Ύπππ
Έπ
½π
Ά π
°π
»π
Άπ
Ύππ
Έππ
·π
Ό π
π
Έπππ
°π
»π
Έππ
΄π</h1>
<li className='list'>
<ul><button className='button' onClick={() => setAlgorithm("selection sort")}>Selection Sort</button></ul>
<ul><button className='button' onClick={() => setAlgorithm("randomize")}>Randomize</button></ul>
</li>
</div>
)
}
export default ControlPanel;
2
Answers
I’m not sure you’re using useContext properly here:
The
dispatch
function should be coming from useReducer not useContext.From the documentation:
Your code has problem with the switch-case statements. Use break or return statements after each case. If you don’t do that next case will also run. And you shouldn’t use setTimeout inside of a reducer. Just return the states from reducer and then handle UI differently.