0

I need to display a single object from inside an array chosen at random. I would usually use .map to display the array out, but instead of displaying the full array, I need to display 1 item at a time, then use a button to cycle through the array objects displayed at random.

Could I get anyone to point me in the right direction? Looking for a solution in javascript for my react app.

Thank you!

Kyle
  • 3,935
  • 2
  • 30
  • 44
  • 1
    Shuffle the array. Keep an array index in a global variable, display that index each time, and increment it. – Barmar Jan 14 '20 at 03:04
  • Does this answer your question? [How can I shuffle an array?](https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array) – danh Jan 14 '20 at 03:07

1 Answers1

0

Here is my hella' over-complicated solution: https://codesandbox.io/s/kind-leavitt-z7hq1

Basically I created a generator to shuffle and iterate the array. Then inside my react component I have a state that stores the current value, and a callback to advance the iterator.

Only caveat is the array instance mustn't change unless you want to start the iteration over. What I mean by this is that you shouldn't pass an array expression to the component as part of a render function. Because during each render, the instance will be new. So make sure that your array is set somewhere statically.

import React, { useState, useMemo, useCallback } from "react";
import "./styles.css";

/**
 * Shuffles array in place. ES6 version
 * https://stackoverflow.com/a/6274381/701263
 * @param {Array} a items An array containing the items.
 */
function shuffle(a) {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

// The * denotes a generator function
function* shuffleIterator(array) {
  // Shuffle the array
  const shuffled = shuffle(array);
  // Iterate it
  for (let item of shuffled) {
    // Yield the item out
    yield item;
  }
}

const MyComponent = ({ array }) => {
  /**
   * Get our iterator, we use memo so that the iterator doesn't
   * get created every render. Only when array changes will the
   * iterator be re-created.
   **/
  const iter = useMemo(() => shuffleIterator(array), [array]);

  // This callback will get the next yielded value from the iterator.
  const getNextState = useCallback(() => {
    const next = iter.next();
    // If done, return false
    if (next.done) {
      return false;
    } else {
      // else return the value
      return next.value;
    }
  }, [iter]);

  /**
   * Setup our item state, telling React to get the intitial
   * value from getNextState.
   **/
  const [item, setItem] = useState(getNextState);

  // Create a callback to be called on our button click
  const next = useCallback(() => {
    // Set the item to the next yieled value
    setItem(getNextState());
  }, [getNextState]);

  // if the value is false, return done
  if (item === false) {
    return <div>Done!</div>;
  } else {
    // Else return our value and a button to advance
    return (
      <div>
        <span>{item}</span>
        <button onClick={next}>Next</button>
      </div>
    );
  }
};

/**
 * NOTE: This array is defined here and passed in to MyComponent.
 * If I had defined the array inside App the instance would be new
 * each time and the app would iterate forever.
 **/
const myArray = [1, 2, 3, 4, 5];

export default function App() {
  return <MyComponent array={myArray} />;
}
Kyle
  • 3,935
  • 2
  • 30
  • 44
  • So this is really great code, but i'm just not sure how to incorporate it.. I havent worked with a generator function, and i havent seen a return like that in the default export at the bottom. I'm also using react-redux, so my default export looks like ``` export default connect(mapStateToProps, { getAllPosts })(Home);```. I played with the code in your code sandbox and it does exactly what I need, and I love that it even prevents already used numbers from being pulled again. But I need the yielded # to be set to a local state value... and i'm really not sure how to make that happen. – KleinBottleCode Jan 14 '20 at 07:15
  • So I managed to use the randomizer function you wrote to update state in a way that works for me, it's not being so fancy as to prevent already used #s from being called again but this is a big step in the right direction. Thank you! – KleinBottleCode Jan 14 '20 at 07:20