skip to Main Content

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


  1. I’m not sure you’re using useContext properly here:

      const [arr, dispatch] = useContext<[number[], React.Dispatch<Action>]>(arrContext);

    The dispatch function should be coming from useReducer not useContext.

    From the documentation:

    import { useContext } from 'react';
    
    function MyComponent() {
      const theme = useContext(ThemeContext);
      // ...
    import { useReducer } from 'react';
    
    function reducer(state, action) {
      // ...
    }
    
    function MyComponent() {
      const [state, dispatch] = useReducer(reducer, { age: 42 });
      // ...
    Login or Signup to reply.
  2. 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.

    case "selection sort":
      ...your code,
      break;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search