Display Only 3 Objects at the time

Viewed 71

I have a quote generator that I am trying to implement a feature of displaying a specific amount of objects at the time. I have tried using map, but it returns that it's not a function. Right now I have it to generate a random quote, but I want to have the option to display a specific number. Any help is greatly appreciated. the is what is working, but I commented out to try out the map.

App.js

import { useState, useEffect } from "react";
import Footer from "./Components/Footer/Footer";
import Quote from "./Components/Quote/Quote";
import "./App.css"
import { data } from "./Components/Data/Data";
import { characterData } from "./Components/Data/CharacterData"
import CharacterBios from "./Components/CharacterBios/CharacterBios";
import Header from "./Components/Header/Header";

function App() {
  const [quote, setQuote] = useState();
  const [isLoading, setIsLoading] = useState(true);
  const randomise = () => {
    const randomNumber = Math.floor(Math.random() * data.length);
    setQuote(data[randomNumber]);
  };
  //math.floor makes it a whole number, and the equation above goes through the data at random
  useEffect(() => {
    randomise();
    setIsLoading(false);
  }, []);

  return (
    <div className="App">
      <Header />
      <div className="main">

      <h1>Quote Generator</h1>
      {isLoading ? <p>Quote now loading...</p> : <Quote data={quote} />}
      <button onClick={randomise}>Generate Quote</button>
      <CharacterBios characterData={characterData} />
      <Footer />
      </div>
    </div>
  );
}

export default App;

Quote.jsx

import React from 'react'


const Quote = ({data}) => {

    return (
        <div className='container quote-section'>

<div className="row">

{data.slice(0).map((item,index)=> (

    <div className="col-xl-4 col-lg-4 col-md-6 col-sm-12" key={index}> 
       
        <div className="marked-content-card">

            <p><span className="name">{item.name}</span></p>
            <p>{item.quote}</p>
        </div>
    </div>

))}



</div>

            {/* <blockquote> {
                data.quote
            }
                <p>-{
                    data.name
                }</p>
            </blockquote> */}



        </div>

    )
}

export default Quote;
2 Answers

Here's what you can do:

  1. Use an array for the quote state
  2. Randomize by:
    • Cloning the quote array -> tempData = [...data]
    • Create an array to hold the quotes to be set -> tempQuotes
    • Iterate for the number of quotes you need and:
      • splice() a quote out of tempQuotes using the randome index
      • push it into tempQuotes
        • Spread ... because splice returns an array
      • Iterate again with the remaining quotes in tempData
  3. Set the new quote state
  4. Map and render

Code Sandbox

import { useState, useEffect } from "react";

const data = [
  "Another one bites the dust",
  "Sweet dreams are made of this",
  "Never gonna give you up",
  "Highway to the danger zone",
  "Let the bodies hit the floor",
  "Hey now, I'm an allstar",
  "Hello darkness my old friend"
];

function App() {
  const [quotes, setQuotes] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const numberOfQuotes = 3;

  const randomise = () => {
    const tempData = [...data];
    const tempQuotes = [];
    for (let i = 0; i < numberOfQuotes; i++) {
      const randomNumber = Math.floor(Math.random() * tempData.length);
      tempQuotes.push(...tempData.splice(randomNumber, 1));
    }
    setQuotes(tempQuotes);
  };
  
  useEffect(() => {
    randomise();
    setIsLoading(false);
  }, []);

  const quoteEls = quotes.map((quote) => <li class="quote">{quote}</li>);

  return (
    <div className="App">
      <div className="main">
        <h1>Quote Generator</h1>
        {isLoading ? <p>Quote now loading...</p> : quoteEls}
        <button onClick={randomise}>Generate Quote</button>
      </div>
    </div>
  );
}

export default App;

If data is large and you don't want to clone it to memory each time, you can:

  1. Make an array of indices i.e. [0,1,2,3] using Array.from()
  2. Randomly splice indices out of it like we did above
  3. map that array to your data

The function below is just for demonstration, but it'll be easy to integrate into your code if decide to do so.

const data = [
  "Another one bites the dust",
  "Sweet dreams are made of this",
  "Never gonna give you up",
  "Highway to the danger zone",
  "Let the bodies hit the floor",
  "Hey now, I'm an allstar",
  "Hello darkness my old friend"
];

const numberOfQuotes = 3

function randomize() {
  const quoteIndices = []
  const indices = Array.from({length: data.length}, (a,i) => i) // Make array of indices
  for (let i = 0; i < numberOfQuotes; i++){
    let randomNumber = Math.floor(Math.random() * indices.length);
    quoteIndices.push(...indices.splice(randomNumber, 1))
  }
  return quoteIndices.map(index => data[index])
}

console.log(randomize())

Yes, .map is not a function because the data array contains object as per your use in the Quote component, and the reason you are getting the error is because you are setting the quote value from a value in your data array. i.e. setQuote(data[randomNumber]); as randomNumber gives an integer. You need to modify your randomise function to store a number of random quotes in your quote state:

const randomise = () => {
  const indices = new Set()
  const N = 3   // specifies number of random items
  while(true){
    const randomNumber = Math.floor(Math.random()*data.length)
    indices.add(randomNumber)
    if(indices.size>=N) break;
  }
  let quotesArr = []
  for(i in indices){
    quotesArr.push(data[i])
  }
  
  setQuote(quotesArr);
  };

Above code keep generating random numbers until you have N number of distinct random numbers, once you have that, you just need to pick the objects in data at those indices, then set the quotes.

Then you will have to update your return statement as well, specifically the part where you are using Quote component:

<>{quote.map(q => <Quote data={q} />)}</>
Related