0

I'm building a flashcard app with React to help with retaining programming concepts. So far, I have the app set up to display a card with a concept definition/explanation on the front of the card and the corresponding term/concept on the back. The user can flip the card and change to a different card with the click of a button. The problem is that currently, the onClick sometimes shows card that was shown immediately before. I want to prevent this from happening. I attempted to do so using a ternary operator but somehow, my Javascript logic is errant because I am still getting repeat displays. How do I fix this?

Here is the code:

// data and components
import { conceptArray } from "./data";
import FlashCard from "./components/FlashCard";

function App() {
  const [randomCard, setRandomCard] = useState({});
  const [mode, setMode] = useState(true);

  // this should store the individual concept (individual items in the concept Array) to later be displayed as a card
  const getCard = () => {
    // this changes the card and posits that there can be no repeat display of card that was displayed immediately before
    let newCard = conceptArray[Math.floor(Math.random() * conceptArray.length)];
    newCard !== randomCard ? setRandomCard(newCard) : newCard = conceptArray[Math.floor(Math.random() * conceptArray.length)];
    // this allows for the front of the card(ie. the definition) to be displayed
    setMode(true);
  };

  const flip = () => {
    // this allows for the back of the card (ie. the term itself) to be displayed
    setMode(!mode);
  }

  console.log(randomCard);

  return (
    <div className="App">
      <header className="App-header">
        <FlashCard randomCard={randomCard} mode={mode} />
        <button onClick={getCard}>Get FlashCard</button>
        <button onClick={flip}>Flip</button>
      </header>
    </div>
  );
}

export default App;
Jevon Cochran
  • 1,565
  • 2
  • 12
  • 23
  • How about keeping an array that stores last three random (digits) and compare each new to the ones you have – Misha Dec 14 '19 at 20:34
  • 1
    This sounds more complicated than it needs to be. But I'm willing to consider this. Can you suggest the code for this? @Misha – Jevon Cochran Dec 14 '19 at 20:40
  • 1
    How about you shuffle the conceptArray into state. Then take the first item in the array every time you pick a card (update state with the rest). When the state is empty, you reshuffle the concept array... – Thomas Wikman Dec 14 '19 at 20:46
  • Wouldn't this essentially just be putting the cards in order @ThomasWikman? I actually want the card order to be randomized. – Jevon Cochran Dec 14 '19 at 20:50
  • If you shuffle the array, the order is random. So picking index 0 of the shuffled array is not the same as 0 of the ordered array. Also, you'd need to go through all unique cards until you could get a new sequence of cards (in a new, shuffled order). You could obviously limit how many cards that you can pick before you re-introduce a seen card again. – Thomas Wikman Dec 14 '19 at 20:52
  • 1
    Think of it like a deck of cards. You shuffle the deck, take the top card. You'll just have to figure out if you want the picked card to go back into the deck again or if you want the deck to be empty before you re-shuffle it. – Thomas Wikman Dec 14 '19 at 21:01
  • Sure. I get that. I'm reading up on the documentation for shuffling arrays and once I understand the technique, I'll give it a shot and let you if I'm successful or not. @ThomasWikman – Jevon Cochran Dec 14 '19 at 21:03

2 Answers2

0

Many solutions can be applied, if you refer to this question on SOF.. kindly check this function maybe you can use it globally if you need random arrays just pass array length

const generate_random_number = (_objLength) => {

let random_array_of_integers = [];

while (random_array_of_integers.length < _objLength) {
    let random_integer = Math.floor(Math.random() * _objLength);
    if (!random_array_of_integers.includes(random_integer))
        random_array_of_integers.push(random_integer);
}


  return random_array_of_integers;

}
Misha
  • 109
  • 8
0

So I ended up going with the suggestion of shuffling the conceptArray into state. I used the Fisher-Yates (aka Knuth) Shuffle to do so, which can be found here: How to randomize (shuffle) a JavaScript array?. I don't completely understand the logic behind it yet but I was able to apply it to my code and get it to work. Now, the cards are being drawn in a random order and there are no immediate repeats.

As Thomas Wikman so succintly explained, this is like shuffling the items in the conceptArray similar to how one would shuffle a deck of cards. Once that happens, I use an onClick to grab the first item from that array, filter out the array to exclude the concept being grabbed and proceed to grab another one. Once I'm done to the last concept in the array, I execute the shuffle again and start over.

In case this helps someone else, here is the resulting code:

// data and components
import { conceptArray } from "./data";
import FlashCard from "./components/FlashCard";
import AddForm from "./components/AddForm";

function App() {
  // Fisher-Yates (aka Knuth) Shuffle
  // don't completely understand it but I got it to work
  const shuffle = array => {
    var currentIndex = array.length, temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

      // Pick a remaining element...
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;

      // And swap it with the current element.
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }

    return array;
  }

  // puts shuffled array of concepts into state
  const [cardArray, setCardArray] = useState(shuffle(conceptArray));
  const [randomCard, setRandomCard] = useState({});
  const [frontMode, setFrontMode] = useState(true);
  const [formMode, setFormMode] = useState(false);

  // stores the individual concept (individual item in the concept Array) to be displayed as a card
  const getCard = () => {
    // grabs the first item in the shuffled array
    setRandomCard(cardArray[0]);
    // filters array so that item already displayed on card cannot be shown again until array is reshuffled
    setCardArray(cardArray.filter(item => item !== cardArray[0]));
    // when there is only one item left in filtered array, shuffles entire array again
    if (cardArray.length === 1) {
      setCardArray(shuffle(conceptArray));
    }
    // this allows for the front of the card(ie. the definition) to be displayed
    setFrontMode(true);
  };

  const flip = () => {
    // this allows for the back of the card (ie. the term itself) to be displayed
    setFrontMode(!frontMode);
  }

  const renderForm = () => {
    setFormMode(true);
  }

  // console.log(randomCard);
  // console.log(conceptArrayRandomized);
  // console.log(conceptArray);


  return (
    <div className="App">
      {!formMode && <FlashCard randomCard={randomCard} frontMode={frontMode} />}
      {!formMode && <button onClick={getCard}>Get FlashCard</button>}
      {!formMode && <button onClick={flip}>Flip</button>}
      <br />
      {!formMode && <button onClick={renderForm}>Add New</button>}
      {formMode && <AddForm />}
    </div>
  );
}

export default App;
Jevon Cochran
  • 1,565
  • 2
  • 12
  • 23