I am making a card game in React JS that requires 3 sets of unique cards.
The way the format works is there are ingredient cards that can create potions. The ingredients are dealt in the Top Row of the game, so I have the component called TopRow.
Since these are not normal playing cards I had to generate arrays with 10 of each of 5 different cards for the deal. ( shuffle(a) )
Then I am splicing the deal to only get 5 cards ( a.splice(5); )
So I want the value of the ingredients to increment based on the number of times the ingredients appear, example: function handleHoneyIncrement should increase countHoney by 1.
I've tried a couple different things and I guess I am having a brain fart on how to make a for loop for this.
function TopRow(props) {
let a=["Honey0", "Bone0", "Herbs0", "Mushroom0", "Seeds0",
"Honey1", "Bone1", "Herbs1", "Mushroom1", "Seeds1",
"Honey2", "Bone2", "Herbs2", "Mushroom2", "Seeds2",
"Honey3", "Bone3", "Herbs3", "Mushroom3", "Seeds3",
"Honey4", "Bone4", "Herbs4", "Mushroom4", "Seeds4",
"Honey5", "Bone5", "Herbs5", "Mushroom5", "Seeds5",
"Honey6", "Bone6", "Herbs6", "Mushroom6", "Seeds6",
"Honey7", "Bone7", "Herbs7", "Mushroom7", "Seeds7",
"Honey8", "Bone8", "Herbs8", "Mushroom8", "Seeds8",
"Honey9", "Bone9", "Herbs9", "Mushroom9", "Seeds9"
];
shuffle(a);
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.splice(5);
}
let imageIngredients = a.map(image => {
return <img key={image} src={require(`../pngs/${image}.png`)}
alt="ingredients" className="img-responsive"
style={{width:"15%", float:"left"}}
/>
});
let handleHoneyIncrement = () => {
if (shuffle.length= "Honey0" ||"Honey1" ||"Honey2" ||"Honey3" ||"Honey4" ||"Honey5" ||"Honey6" ||"Honey7" || "Honey8" || "Honey9" ){
this.setState({countHoney: this.state.countHoney + 1})
};
};
return (
<div className="row" id="topRow"
style={{WebkitBorderRadius:2, WebkitTextStrokeColor: "red", width:"90%", maxHeight:"30%", padding:0}} >
<div className="col-6-md">
<img src={require('../pngs/IngredientBacks.png')} alt="ingredientsBack" style={{width:"15%", float:"left"}} />
</div>
<div className="col-6-md">
{imageIngredients}
{handleHoneyIncrement}
{a}
</div>
</div>
);}
export default TopRow;
Not 100% sure if this is what you were going for, but it sounds like you just need to turn the ingredients list into a collection of ingredient/count pairs?
const ingredientCounts = a.reduce((obj, curr) => ({
...obj,
[curr]: obj[curr] ? obj[curr] + 1 : 1
}), {})
ingredientCounts["Honey0"] // 1
If you're looking to count all Honeys together like Honey0 + Honey1, etc., this should work:
const ingredientCounts = a.reduce((obj, curr) => {
const keys = ["Honey", "etc"]; // maybe this list should be somewhere else, IDK
const key = keys.find(k => curr.includes(k)); // missing null check :)
return {
...obj,
[key]: obj[key] ? obj[key] + 1 : 1
}
}, {})
ingredientCounts["Honey"] // 10
Then we can set state for all of them like:
this.setState({
counts: ingredientCounts
})
And have a state of counts like:
{
Honey: 10,
etc: 0
}
I'm not 100% sure that I understand your goals correctly, but I think a simplified version is that you want to show:
5 random cards from your deck
A button or trigger that shuffles the deck and displays a new hand of 5 cards from the same deck
A count of the total number of honey cards accumulated as the hand is updated
There are a number of confusing things in your code sample, so rather than try to make corrections I threw up a quick demo of how I would approach that problem with some comments explaining what I did differently, given these assumptions. https://codesandbox.io/s/trusting-mclean-kwwq4
import React, { useState, useEffect } from "react";
// The deck of cards is probably a constant whose values never change directly.
// It's possible that I'm wrong and the deck *does* change, but even so I imagine
// it would come from a prop or context from a parent component. Either way the
// cards array should not be mutable.
const CARDS = [
"Honey0", "Bone0", "Herbs0", "Mushroom0", "Seeds0",
"Honey1", "Bone1", "Herbs1", "Mushroom1", "Seeds1",
"Honey2", "Bone2", "Herbs2", "Mushroom2", "Seeds2",
"Honey3", "Bone3", "Herbs3", "Mushroom3", "Seeds3",
"Honey4", "Bone4", "Herbs4", "Mushroom4", "Seeds4",
"Honey5", "Bone5", "Herbs5", "Mushroom5", "Seeds5",
"Honey6", "Bone6", "Herbs6", "Mushroom6", "Seeds6",
"Honey7", "Bone7", "Herbs7", "Mushroom7", "Seeds7",
"Honey8", "Bone8", "Herbs8", "Mushroom8", "Seeds8",
"Honey9", "Bone9", "Herbs9", "Mushroom9", "Seeds9"
];
const initialCards = [];
function TopRow(props) {
// Keep the current hand of cards in state rather than mutating an array
// directly in the function body. React function components should be pure,
// with all side effects occurring inside of effect hooks.
let [cards, setCards] = useState(initialCards);
let [honeyCount, setHoneyCount] = useState(
countSubstrings(initialCards, "Honey")
);
let imageIngredients = cards.map(image => (
<img
key={image}
src={require(`../pngs/${image}.png`)}
alt={humanReadableAltTag}
className="img-responsive"
style={{ width: "15%", float: "left" }}
/>
));
function shuffleCards() {
// Reset your hand of cards with the original array (the deck)
setCards(shuffleArray(CARDS));
}
// Return all state to initial values
function reset() {
setCards(initialCards);
setHoneyCount(countSubstrings(initialCards, "Honey"));
}
// Any time our cards are updated, we want to increment the number of Honey
// cards in our hand. useState accepts a lazy initializer to access the
// previous state, which is very useful for effects like this!
useEffect(() => {
setHoneyCount(count => count + countSubstrings(cards, "Honey"));
}, [cards]);
return (
<div
{...props}
className="row"
id="topRow"
style={
{
WebkitBorderRadius: 2,
WebkitTextStrokeColor: "red",
width: "90%",
maxHeight: "30%",
padding: 0
}
}
>
<button onClick={shuffleCards}>
{cards.length ? "Shuffle" : "Deal"}
</button>
<button onClick={reset}>Reset</button>
<hr />
<div className="col-6-md">
<img
src={require("../pngs/IngredientBacks.png")}
alt="Back of ingredient card"
style={{ width: "15%", float: "left" }}
/>
</div>
<div className="col-6-md">
{imageIngredients}
</div>
<hr />
<div>
<strong>TOTAL HONEY COUNT:</strong> {honeyCount}
</div>
</div>
);
}
export default TopRow;
// I put these utility functions outside of the component body since there is no
// real reason to recreate them on each render.
/**
* #param {any[]} array
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
// Use slice instead of splice here to prevent mutating the original array
return array.slice(0, 5);
}
/**
* #param {string[]} array
* #param {string} subs
*/
function countSubstrings(array, subs) {
return array.filter(card => card.includes(subs)).length;
}
Related
I'm making a blackjack hand simulation and I've encountered an issue with my code.
The game goes like this: users gets two random cards and a total of the points, clicks 'hit' to get another random card from the deck. Now that's all working but there's one more rule: if that card is an Ace, user chooses if they want to get 1 or 10 points. I implemented it before when I only had one card at a time with useEffect, however now I refactored my code and the total isn't kept in useState + the array has two cards that need to evaluated, not the most recent one.
I've tried putting my loop and if statement in a useEffect and conditionally render the Popup to let the user decide (with and without dependencies), but when I put the useState() to trigger the condition, it throws an error that there have been too many renders and I'm not sure why that is.
Here's my Home component:
import {useState, useEffect} from 'react'
import Card from '../components/Card';
import Total from '../components/Total';
import Popup from '../components/Popup'
import {shuffle} from '../hooks/shuffleCards'
import {deckArray} from '../utils/data'
export default function Home(){
const startHandSize = 2
const [starterDeck, setStarterDeck] = useState(shuffle(deckArray))
const [howManyDealt, setHowManyDealt] = useState(startHandSize)
const [triggerPopup, setButtonPopup] = useState(false)
const deal = () => {
setHowManyDealt(startHandSize)
setStarterDeck(shuffle(deckArray))
}
const hit = () => !bust && setHowManyDealt(prev => prev + 1)
const usersCards = starterDeck.slice(-howManyDealt)
var total = 0
usersCards.forEach(function (arrayItem) {
if(arrayItem.card === "A"){
alert("you have an ace")
}
else{
total += arrayItem.value
}
});
const bust = total > 21;
return(
<div>
<button onClick={deal}>DEAL</button>
<button disabled={bust} onClick={hit}>HIT</button>
<button disabled={bust}>STAND</button>
<Total total={total} usersCards={usersCards}/>
<Card usersCards={usersCards}/>
{triggerPopup && <Popup total={total} setButtonPopup={setButtonPopup}/>}
</div>
)
}
and my Popup:
export default function Popup({total, setButtonPopup}){
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
return(
<div className="popup">
<div className="popup-inner">
<h4>You've got an Ace. Would you like to collect 1 or 10 points?</h4>
<button className=".btn-popup" onClick={() => handleClick(1)}>1 POINT</button>
<button className=".btn-popup" onClick={() => handleClick(10)}>10 POINTS</button>
</div>
</div>
)
}
Any help much appreciated!
Good attempt. However, there seems to be a general misunderstanding about state. Consider this code:
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
total is a purely local variable to Popup, so this += pretty much does nothing. To change state in the caller, you'd normally pass a callback that can trigger a setState and move the new value for total into state.
Remember: any data change must happen immutably, and if you want to trigger a re-render, you have to set state. Of course, there are ways to circumvent this flow using refs and so forth, but these are escape hatches you shouldn't use if you don't have to.
However, a design with total kept in state strikes me as redundant. We already know the total based on the cards in play. A better strategy seems to be having ace values individually settable via the popup modal, assuming you don't want to auto-compute these ace values to be as high as possible without busting or use a toggle switch instead of a modal.
I kept going with my code from your previous question and added the modal. I'm treating high aces as 11 per the rules of Blackjack, but you can easily make that 10 if you want.
As before, I'm hoping you can apply the techniques here to your code. The keys are the handleAceSet callback and the new piece of state aceToSet, which is a ace the user has picked, or null if the user hasn't chosen an ace. aceToSet is like your setButtonPopup, but tracks an object or null rather than a boolean. When aceToSet isn't null, the user has selected an ace and we show the modal to let them pick a value for it.
handleAceSet may seem a bit complex, but it has to be due to immutability. It finds the index of the ace the user wants to set in the deck array, then creates a new object at this index with the new value and glues the subarray slices before and after the index back together.
// utility library "import"
const cards = (() => {
const shuffle = a => {
a = a.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = ~~(Math.random() * (i + 1));
const x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
};
const frz = (...args) => Object.freeze(...args);
const suits = frz([..."HCSD"]);
const faces = frz([..."AJQK"]);
const pips = frz([...Array(9)].map((_, i) => i + 2));
const ranks = frz([...pips, ...faces]);
const cards = frz(
suits.flatMap(s =>
ranks.map(r =>
frz({
rank: r,
suit: s,
str: r + s,
value: isNaN(r) ? (r === "A" ? 1 : 10) : r,
})
)
)
);
const shuffled = () => shuffle(cards);
return {shuffled};
})();
const {Fragment, useState} = React;
const AceSetterModal = ({handleSetLow, handleSetHigh}) => (
<div>
<button onClick={handleSetLow}>Set ace low</button>
<button onClick={handleSetHigh}>Set ace high</button>
</div>
);
const Card = ({card, handleAceSet}) => (
<div>
{card.str}
{card.rank === "A" && (
<Fragment>
{" "}
<button onClick={handleAceSet}>
Set ({card.value})
</button>
</Fragment>
)}
</div>
);
const Game = () => {
const startHandSize = 2;
const goal = 21;
const lowAce = 1;
const highAce = 11;
const [deck, setDeck] = useState(cards.shuffled());
const [cardsDealt, setCardsDealt] = useState(startHandSize);
const [aceToSet, setAceToSet] = useState(null);
const handleAceSet = value => {
setDeck(deck => {
const i = deck.findIndex(e => e.str === aceToSet.str);
return [
...deck.slice(0, i),
{...aceToSet, value},
...deck.slice(i + 1),
];
});
setAceToSet(null);
};
const deal = () => {
setCardsDealt(startHandSize);
setDeck(cards.shuffled());
};
const hit = () => !bust && setCardsDealt(prev => prev + 1);
const cardsInPlay = deck.slice(-cardsDealt);
const total = cardsInPlay.reduce((a, e) => a + e.value, 0);
const bust = total > goal;
return (
<div>
{aceToSet ? (
<AceSetterModal
handleSetLow={() => handleAceSet(lowAce)}
handleSetHigh={() => handleAceSet(highAce)}
/>
) : (
<Fragment>
<button onClick={deal}>Deal</button>
<button disabled={bust} onClick={hit}>
Hit
</button>
<div>
{cardsInPlay.map(e => (
<Card
key={e.str}
handleAceSet={() => setAceToSet(e)}
card={e}
/>
))}
</div>
<div>Total: {total}</div>
<div>{bust && "Bust!"}</div>
</Fragment>
)}
</div>
);
};
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Game />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
I made this React application with typescript for practicing. It contains a counter. Small circles supposed to be indicate the tens, while big ones the hundreds of counter.
This is my code:
`
import {useEffect, useState} from 'react';
export default function Counter(props: { startCount: number }): JSX.Element {
const [result, setResult] = useState<number>(props.startCount);
const [extraElementArray, setExtraElementArray] = useState<Array<JSX.Element>>([]);
const tenElement: JSX.Element = <div className='extra-element--ten'>O</div>;
const hundredElement: JSX.Element = <div className='extra-element--hundred'>O</div>;
const handleClick = (amount: number) => {
setResult(prevResult => prevResult + amount);
}
useEffect(
() =>
setExtraElementArray(() => {
let resArray: Array<JSX.Element> = [];
for (let i = 1; i <= Math.floor(result / 100); i++) {
resArray.push(hundredElement);
}
for (let i = 0; i < Math.floor(result / 10) % 10; i++) {
resArray.push(tenElement);
}
return resArray;
}), [result]);
return (
<>
<div className='counter-container'>
<div className='minus-button' onClick={() => handleClick(-1)}>-</div>
<div className='result-display'>{result}</div>
<div className='plus-button' onClick={() => handleClick(1)}>+
</div>
</div>
<div className='extra-element-container'>
{extraElementArray}
</div>
</>
);
}
`
I used two for cycles that works perfectly but I affraid this is not the best neither the most elegant approach.
I've tried out the solution that is recommended in all of the topics with similar problems e.g. tihs one link, but Array(number).fill(element) didn't work for me since the calcualted number that initialize the array can be zero.
I'm wondering if there is a solution javascript / typescript that repeat an array element n times and concat to the array. Something like this: array.pushRepeat(element, n). If n = 0 gives an empty array
Instead of showing every single item inside 'portfolioComponents', I want to show a specific amount of items, from a specific number ('startArrayHere') in the array, and raise and lower from which number ('startArrayHere') I start showing items from the Array. I know the For-Loop is wrong, but I just can't figure out how to do it - can anybody help me out here?
class Portfolio extends Component {
constructor(){
super ()
this.state = {
portitems: portfolioItems,
startArrayHere: 0,
amountOfItemsToShow: 6
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
if(this.state.startArrayHere < (portfolioItems.length -
this.state.amountOfItemsToShow))
this.setState(prevState => {
return {
startArrayHere: startArrayHere + 1
}
})
}
render(){
const portfolioComponents = this.state.portitems.map(item =>
<PortfolioTemp key={item.id} portfolioitem={item} />)
return (
<div>
<button onClick={() => this.handleClick()}>STATE CHANGE</button>
<div className="portfolio-container">
{
for (let i = 0; i < this.state.amountOfItemsToShow; i++){
portfolioComponents[i + this.state.startArrayHere]
}
}
</div>
</div>
)
}
}
export default Portfolio;
To get a subset of an Array in JS, use the .slice method
Array.slice(startIndexInclusive, endIndexExclusive)
Reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Example:
const a = ['Hello', 'World', 'Foo', 'Bar']
console.log(a.slice(0, 1)) // prints: ['Hello']
console.log(a.slice(0, 2)) // prints: ['Hello', 'World']
console.log(a.slice(2, a.length) // prints: ['Foo', 'Bar']
console.log(a.slice(0, a.length) // prints the entire array ... note: this would be pointless, as you could just print 'a' itself
So, to incorporate your amountOfItems you'd have to just do
a.slice(startIndex, startIndex + amountOfItems)
I hope this helps somewhat.
You can create a shallow copy of an array between any two indicies with Array.prototype.slice(). In practice, you would use this to make a truncated copy of portfolioItems from startArrayHere to startArrayHere + amountOfItemsToShow + 1, and render those items only.
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;
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;
}
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;
In my application, I randomized the sequence of specific views. However, I have a problem with displaying the correct sequence number of view.
So, the first view should get number 1, second view number 2, etc.. Since they get shuffled in an array, I have no idea how to access the order number of the view beforehand. The correct number should then get passed as a prop to the specific view component in order to display it.
shuffle(arr) {
var i, j, temp;
for (i = arr.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
};
constructor() {
const componentArray = [<Tasks incrementSequenceCounter={this.incrementSequenceCounter} count={this.state.count} />, <APM incrementSequenceCounter={this.incrementSequenceCounter} count={this.state.count} />, <ICAA incrementSequenceCounter={this.incrementSequenceCounter} count={this.state.count} />]
const shuffledArray = this.shuffle(componentArray);
this.state.shuffledArray = shuffledArray;
}
Somehow the component should be aware of its index in the array, but I don't know where to start.
Firstly I don't like the idea of initialising components in the constructor. That basically makes them static, but that may be what you're after. Here's my attempt:
constructor() {
const componentArray = [
{
type: Tasks,
props: {
incrementSequenceCounter: this.incrementSequenceCounter,
count: this.state.count
}
},
{
type: APM,
props: {
incrementSequenceCounter: this.incrementSequenceCounter,
count: this.state.count
}
},
{
type: ICAA,
props: {
incrementSequenceCounter: this.incrementSequenceCounter,
count: this.state.count
}
}
const shuffledArray = this.shuffle(componentArray);
this.state.shuffledArray = shuffledArray.map(
(componentConstructor, index) => React.createElement(componentConstructor.type, { ...componentConstructor.props, index })
);
}
The basic idea is to construct them after you've determined the order. The obvious disadvantage here is that since this is happening in the constructor any state changes in the parent component are not reflected in these children components. If you don't want that then this should be moved in render and/or componentDidMount/Update
Note: Based on other answers I need to clarify that in my understanding the question is how to pass the index the component ends up in after shuffling to the component itself. This differs from how others have answered so if I am understanding it incorrectly let me know
Your array's element should be wrapped in an object before shuffling:
const componentArray = [
{
index: 0,
component: <MyComponent />
}
];
You could create it from your array with a simple map():
const indexArray = componentArray.map((el, i) => ({ index: i, component: el });
Then you can safely shuffle your indexArray