Search code examples
javascriptreactjssortingrandom-seedcard

How can I sort a list of cards with randomly generated numbers inside of them?


I generate cards on a button press. These cards have a randomly generated number between 0 and 100. I am trying to setup a component or function that would allow me to sort all of these cards in numerical order ascending or descending, or both, when I click the sort button. I have tried the following code. Keep in mind, this is all contained within the same App component.

The random numbers are generated in the add card component.

I feel like I'm super close, but I can't figure out what I'm missing.


  const sortTypes = {
    up: {
      class: "sort-up",
      fn: (a, b) => a.number - b.number,
    },

    down: {
      class: "sort-down",
      fn: (a, b) => b.number - a.number,
    },

    default: {
      class: "sort",
      fn: (a, b) => a,
    },
  };

  const sortAll = () => {
    state = {
      currentSort: 'default'
    };

    onSortChange = () => {
      const { currentSort } = this.state;
      let nextSort;

      if (currentSort === 'down') nextSort = 'up';
      else if (currentSort === 'up') nextSort = 'default';
      else if (currentSort === 'default') nextSort = 'down';

      this.setState({
        currentSort: nextSort
      });
    };

    };
  

  return (
    <body>
      <header>
        <div className="ui buttons">
          <button type="button" onClick={addCard} className="ui button mb-1 mt-1 mr-1"><i className="plus icon"></i>Add Card</button>
          <div className="or mb-1 mt-1"></div>
          <button type="button" onClick={sortAll} className="ui positive button mb-1 mt-1 mr-1"><i className="redo icon"></i>Sort All</button>
        </div>
      </header>


Solution

  • Issues

    1. Functional components are "instanceless" so there is no defined this to reference.
    2. onSortChange is the button onClick handler you want to use to update the current sort.

    Solution

    1. Move the currentSort to component state in a useState hook.

      const [currentSort, setCurrentSort] = useState("default");
      
    2. Fix the onSortChange handler to correctly update state.

      const onSortChange = () => {
        let nextSort;
      
        if (currentSort === "down") nextSort = "up";
        else if (currentSort === "up") nextSort = "default";
        else if (currentSort === "default") nextSort = "down";
      
        setCurrentSort(nextSort);
      };
      
    3. Use an in-line "sort" in the render return. Remember that array.prototype.sort is an in-place sort, i.e. it mutates the array. To get around accidentally mutating state first copy the array.

      {cards
        .slice() // <-- copy
        .sort(sortTypes[currentSort].fn) // <-- select sort function
        .map((cardNumber, index) => (
          <MainCard
            number={cardNumber.number}
            key={cardNumber.id}
            onRemove={() => removeCard(cardNumber.id)}
          />
        ))}
      
    4. Attach correct handler to button

      <button
        type="button"
        onClick={onSortChange}
        className="ui positive button mb-1 mt-1 mr-1"
      >
        <i className="redo icon"></i>Sort All
      </button>
      

    Demo

    Edit how-can-i-sort-a-list-of-cards-with-randomly-generated-numbers-inside-of-them

    Full Code:

    const generateId = (seed = 0) => () => seed++;
    
    const getRandomNumber = function (min, max) {
      let getRandom = Math.floor(Math.random() * max + min);
      return getRandom;
    };
    
    const sortTypes = {
      up: {
        class: "sort-up",
        fn: (a, b) => a.number - b.number
      },
    
      down: {
        class: "sort-down",
        fn: (a, b) => b.number - a.number
      },
    
      default: {
        class: "sort",
        fn: (a, b) => a
      }
    };
    
    const MainCard = ({ number, onRemove }) => {
      return (
        <div className="card">
          <button
            onClick={onRemove}
            className="ui mini red basic icon button"
            style={{
              position: "absolute",
              top: "0",
              right: "0"
            }}
          >
            X
          </button>
          {number}
        </div>
      );
    };
    
    export default function App() {
      const [cards, setCards] = useState([]);
      const [currentSort, setCurrentSort] = useState("default");
    
      const addCard = () => {
        setCards((cards) => [
          ...cards,
          {
            id: generateId(),
            number: getRandomNumber(0, 101)
          }
        ]);
      };
    
      const removeCard = (id) => {
        setCards((cards) => cards.filter((el) => el.id !== id));
      };
    
      const onSortChange = () => {
        let nextSort;
    
        if (currentSort === "down") nextSort = "up";
        else if (currentSort === "up") nextSort = "default";
        else if (currentSort === "default") nextSort = "down";
    
        setCurrentSort(nextSort);
      };
    
      return (
        <body>
          <header>
            <div className="ui buttons">
              <button
                type="button"
                onClick={addCard}
                className="ui button mb-1 mt-1 mr-1"
              >
                <i className="plus icon"></i>Add Card
              </button>
              <div className="or mb-1 mt-1"></div>
              <button
                type="button"
                onClick={onSortChange}
                className="ui positive button mb-1 mt-1 mr-1"
              >
                <i className="redo icon"></i>Sort All
              </button>
            </div>
          </header>
    
          <div className="card-container">
            {cards
              .slice()
              .sort(sortTypes[currentSort].fn)
              .map((cardNumber) => (
                <MainCard
                  number={cardNumber.number}
                  key={cardNumber.id}
                  onRemove={() => removeCard(cardNumber.id)}
                />
              ))}
          </div>
    
          <aside className="showHide"></aside>
    
          <footer>
            <h3 className="text-center text-muted">Footer</h3>
          </footer>
        </body>
      );
    }