I have the following component:-
import React, { useState, useEffect } from "react"
import Card from "./Card"
import joker from "../../assets/joker.png"
import goalie from "../../assets/goalie.png"
import defender from "../../assets/defender.png"
import midfielder from "../../assets/midfielder.png"
import attacker from "../../assets/attacker.png"
const CardsBoard = () => {
const deckLimits = {
joker: 4, goalkeepers: 12, defenders: 12, midfielders: 12, attackers: 12
};
const [ratingObj, setRatingObj] = useState({});
const [visible1, setVisible1 ] = useState(false);
const [visible2, setVisible2 ] = useState(false);
const [deck, setDeck] = useState([]);
const [deck1, setDeck1] = useState([]);
const [deck2, setDeck2] = useState([]);
const [currCardPl1, setCurrCardPl1] = useState({});
const [currCardPl2, setCurrCardPl2] = useState({});
useEffect(() => {
generateDeck();
distributeCards();
}, [deck]);
const startGame = () => {
//reset decks
setDeck([]);
setDeck1([]);x
setDeck2([]);
//generate the card’s values
generateDeck();
if (deck.length > 0) {
distributeCards();
if (currCardPl1 != undefined){
console.log(currCardPl1);
//unMask ratings of Player 1
setVisible1(true);
setVisible2(false);
}
}
};
const distributeCards = () => {
//randomize the deck
deck.sort(() => Math.random() - 0.5);
//distribute 26 cards at random when the game start
const splitDeck1 = deck.slice(0, 26);
const splitDeck2 = deck.slice(26, 52);
//add this card to the deck
splitDeck1.map((card) => {
setDeck1(deck1 => [...deck1, card]);
});
//add this card to the deck
splitDeck2.map((card) => {
setDeck2(deck2 => [...deck2, card]);
});
//queue the first card to Player 1
setCurrCardPl1(deck1[0]);
//queue the first card to Player 2
setCurrCardPl2(deck2[0]);
};
const generateDeck = () => {
for (let i = 0; i < deckLimits.joker; i++) {
generateCard('joker');
};
for (let i = 0; i < deckLimits.goalkeepers; i++) {
generateCard('goalkeeper');
};
for (let i = 0; i < deckLimits.defenders; i++) {
generateCard('defender');
};
for (let i = 0; i < deckLimits.midfielders; i++) {
generateCard('midfielder');
};
for (let i = 0; i < deckLimits.attackers; i++) {
generateCard('attacker');
};
}
const generateCard = item => {
const card = {
player: 0,
image: getImage(item),
title: item.toUpperCase(),
ratings: [
{ title: "Handling", rating: "99" },
{ title: "Reflexes", rating: "99" },
{ title: "Defending", rating: "99" },
{ title: "Strength", rating: "99" },
{ title: "Passing", rating: "99" },
{ title: "Flair", rating: "99" },
{ title: "Finishing", rating: "99" },
{ title: "Composure", rating: "99" },
],
}
//add this card to the deck
setDeck(deck => [...deck, card]);
}
const getImage = item => {
switch (item) {
case "joker":
return joker;
case "goalkeeper":
return goalie;
case "defender":
return defender;
case "midfielder":
return midfielder;
case "attacker":
return attacker;
default:
break;
}
};
return (
<div className="container-fluid justify-content-center">
<div className="row">
<div className="col-md-6">
<div>
<h4>Player 1</h4>
</div>
<Card
cardInfo={currCardPl1}
showRatings={visible1}
onClick={ratingObj => setRatingObj(ratingObj)}
/>
</div>
<div className="col-md-6">
<h4>Player 2</h4>
<Card
cardInfo={currCardPl2}
showRatings={visible2}
onClick={ratingObj => setRatingObj(ratingObj)}
/>
</div>
</div>
<div className="row top-buffer">
<div className="col-md-12 text-center">
<button
id="startGameBtn"
name="StartGameButton"
className="btn btn-secondary"
onClick={() => startGame()}
>
START GAME
</button>
</div>
</div>
</div>
)
}
export default CardsBoard
And I am getting the error:-
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
I am suspecting it has to do something with the Submit button (onClick) which is calling the setState multiple times, however I cannot seem to fix the problem.
So I have the following questions :-
How can I fix the onClick()?
I would like to click on the Start Game button, and trigger generateDeck(); and distributeCards(); so that I get the deck, deck1 and deck2 filled up. Is there a way to do it? At the moment I have created a useEffect() which is dependant on the deck being filled up, is that the correct way to doing it with hooks?
Thanks for your help and time!
You have deck as a dependency in your useEffect hook. That means generateDeck will run every time the deck is updated. However, generateDeck calls generateCard, which calls setDeck. That causes deck to change, which means you're stuck in a sort of infinite loop. You should tie generateDeck to the onClick handler or some other event which will not be triggered by deck changing.
Also, be careful with this:
deck.sort(() => Math.random() - 0.5)
Calling sort will mutate your state directly, which is something you should avoid in React. Try something like setDeck(d=>[...d].sort(/* Your sorting function here */)) instead.
You are using setDeck() in distrubuteCards and in your useEffect hook, you call again the distributeCards
const distributeCards = () => {
// bla bla
setDeck(deck => [...deck, card]);
}
Which results in calling the hook all the time.
useEffect(() => {
generateDeck();
distributeCards();
}, [deck]);
deck.sort(() => Math.random() - 0.5);
You have a problem here.
Save it inside a variable. Otherwise you update the state all the time it's called.
Related
Whilst I am doing cart in react, I have no idea why I keep getting NaN value - only from a specific object data.
When I have the following data list:
#1 ItemsList.js
export const ItemsList = [
{
id: 1,
name: "VA-11 Hall-A: Cyberpunk Bartender Action",
price: 110000,
image: cover1,
link: "https://store.steampowered.com/app/447530/VA11_HallA_Cyberpunk_Bartender_Action/?l=koreana",
},
...
{
id: 6,
name: "Limbus Company",
price: 110000,
image: cover6,
link: "https://limbuscompany.com/",
},
];
And the following code, please look at the comment line.
#2 Goods.jsx
import React, { useContext } from "react";
import "./Goods.css";
import { DataContext } from "../../components/context/DataContext";
export const Goods = (props) => {
const { id, name, price, image, link } = props.shopItemProps;
const { cartItems, addItemToCart, removeItemFromCart } =
useContext(DataContext);
const cartItemStored = cartItems[id];
return (
<div className="goods">
<div className="goods-id">{id}</div>
<img src={image} alt="thumbnail_image" className="goods-image" />
<div className="goods-name">{name}</div>
<div className="goods-price">${price}</div>
<button>
<a href={link} className="goods-link">
Official Store Page
</a>
</button>
<div className="cart-button">
<button onClick={() => removeItemFromCart(id)}>-</button>
// ★Maybe here? but why do I get NaN only for id:6? Others work well.
{cartItemStored > -1 && <> ({cartItemStored}) </>}
<button onClick={() => addItemToCart(id)}>+</button>
</div>
</div>
);
};
What should I do to solve NaN? There seems to be no way to make that value as int in this case. Or do you see any problem from the above code block?
Edited
Sorry for confusing you. Here are the additional code related.
#3. DataContext.js (where cartItems state exists)
import React, { createContext, useState } from "react";
import { ItemsList } from "../ItemsList";
export const DataContext = createContext(null);
const getDefaultCart = () => {
let cart = {};
for (let i = 1; i < ItemsList.length; i++) {
cart[i] = 0;
}
return cart;
};
export const DataContextProvider = (props) => {
const [cartItems, setCartItems] = useState(getDefaultCart);
const checkoutTotalSum = () => {
let totalAmount = 0;
for (const item in cartItems) {
if (cartItems[item] > 0) {
let itemInfo = ItemsList.find((product) => product.id === Number(item));
totalAmount += cartItems[item] * itemInfo.price;
}
}
return totalAmount;
};
const addItemToCart = (itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] + 1 }));
};
const removeItemFromCart = (itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] - 1 }));
};
const updateCartItemCount = (newAmount, itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: newAmount }));
};
const contextValue = {
cartItems,
addItemToCart,
removeItemFromCart,
updateCartItemCount,
checkoutTotalSum,
};
// console.log(cartItems);
return (
<DataContext.Provider value={contextValue}>
{props.children}
</DataContext.Provider>
);
};
The issue is in the function you are using to set the initial value of cartItems, more specifically, in the for loop. This line is the culprit: i < ItemsList.length, when in your case, it should be i <= ItemsList.length. Why? because you are not including the last element of ItemsList on the cart object (you are initializing the i counter with 1 and ItemsList's length is 6).
So, when you call addItemToCart:
const addItemToCart = (itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] + 1 }));
};
And try to update the value corresponding to the last element of ItemsList which is 6 in cartItems, you're getting: '6': undefined + 1 because again, you did skip the last element in the for loop. This results in NaN.
You also have the option of initializing i with 0 and preserve this line: i < ItemsList.length, or:
for (let i = 1; i < ItemsList.length + 1; i++) {
...
}
I'm building a shopping cart where the user can change the amount they want to invest per asset, and then see how much their investment is worth when compounded 10% over 5 years.
I first have my Checkout component:
import React, { useEffect, useState } from "react";
import CartInner from "./CartInner";
export default function Checkout() {
const [cart, setCart] = useState<any>([
{ price: 2000, id: 1 },
{ price: 4000, id: 2 }
]);
const [projectedGrowth, setProjectedGrowth] = useState<any>([]);
const handleOnChange = (index: any, e: any) => {
const newValue = {
price: Number(e.target.value),
id: index + 1,
property_id: index + 1
};
const newArray = [...cart];
newArray.splice(index, 1, newValue);
setCart(newArray);
};
const getGrowthAmount = (index: any) => {
console.log(cart[index].price, "default values for price");
const newValue = calcTotalCompoundInterest(cart[index].price, 5, 10);
const newArray = [...projectedGrowth];
newArray.splice(index, 1, newValue);
setProjectedGrowth(newArray);
};
// whenever the cart is updated call the getGrowthAmount function
useEffect(() => {
for (let index = 0; index < cart.length; index++) {
getGrowthAmount(index);
}
}, [cart]);
console.log(projectedGrowth, "projectedGrowth");
console.log(cart, "cart");
return (
<>
<form className="grid gap-8 grid-cols-3">
<div className="col-span-2">
{cart.map((element, index) => {
return (
<fieldset key={index}>
<CartInner
// projectedGrowth={projectedGrowth[index]}
element={element}
handleOnChange={(e) => handleOnChange(index, e)}
defaultValues={cart[index]?.price}
/>
</fieldset>
);
})}
</div>
<button>Proceed to payment</button>
</form>
</>
);
}
Which allows the user to change their investment amount and calculate compound interest:
function calcTotalCompoundInterest(total: any, year: any, rate: any) {
var interest = rate / 100 + 1;
return parseFloat((total * Math.pow(interest, year)).toFixed(4));
}
My problem is only the second value is getting updated, not the first. E.g if the first input I write 100 and the second 100,000 then the projectedGrowth array will return:
(2) [6442.04, 16105.1]
Which is correct for the second amount (but not for the first).
Here's the Codesandbox
Here's the child component:
import PureInput from "./PureInput";
import React, { useEffect, useState, useMemo } from "react";
const CartInner = React.forwardRef(
(
{
handleOnChange,
price,
projectedGrowth,
defaultValues,
id,
...inputProps
}: any,
ref: any
) => {
return (
<input
min={200}
max={price}
handleOnChange={handleOnChange}
type="number"
step={200}
defaultValue={defaultValues}
id={id}
ref={ref}
{...inputProps}
/>
);
}
);
export default CartInner;
What am I doing wrong here?
How can I get the array to return the correct compounded values (both Onload and Onchange when the user enters into the input)?
Issue is with the splice. accessing the index directly from the array works
const getGrowthAmount = (index: any) => {
console.log(cart[index].price, "default values for price");
const newValue = calcTotalCompoundInterest(cart[index].price, 5, 10);
console.log(newValue, "newValue");
projectedGrowth[index] = newValue;
console.log(projectedGrowth, "list");
setProjectedGrowth(projectedGrowth);
};
Demo
The correct code is this one:
import React, { useEffect, useState, useMemo } from "react";
import { useForm, Controller } from "react-hook-form";
import CartInner from "./CartInner";
function calcTotalCompoundInterest(total: any, year: any, rate: any) {
var interest = rate / 100 + 1;
return parseFloat((total * Math.pow(interest, year)).toFixed(4));
}
export default function Checkout() {
const [cart, setCart] = useState<any>([
{ price: 2000, id: 1 },
{ price: 4000, id: 2 }
]);
const [projectedGrowth, setProjectedGrowth] = useState<any>([]);
const onSubmit = async (data: any) => {};
const handleOnChange = (index: any, e: any) => {
console.log(index);
const newValue = {
price: Number(e.target.value),
id: index + 1,
property_id: index + 1
};
const newArray = [...cart];
newArray[index] = newValue;
setCart(newArray);
};
const getGrowthAmount = () => {
const newArray = [...projectedGrowth];
for (let index = 0; index < cart.length; index++) {
console.log(index);
//console.log(cart[index].price, "default values for price");
console.log(cart);
const newValue = calcTotalCompoundInterest(cart[index].price, 5, 10);
newArray[index] = newValue;
//newArray.splice(index, 1, newValue);
}
setProjectedGrowth(newArray);
};
// whenever the cart is updated call the getGrowthAmount function
useEffect(() => {
//for (let index = 0; index < cart.length; index++) {
getGrowthAmount();
//}
}, [cart]);
console.log(projectedGrowth, "projectedGrowth");
console.log(cart, "cart");
return (
<>
<form className="grid gap-8 grid-cols-3">
<div className="col-span-2">
{cart.map((element, index) => {
return (
<fieldset key={index}>
<CartInner
// projectedGrowth={projectedGrowth[index]}
element={element}
handleOnChange={(e) => handleOnChange(index, e)}
defaultValues={cart[index]?.price}
/>
</fieldset>
);
})}
</div>
<div className="bg-slate-200 p-8 flex flex-col items-stretch">
<button>Proceed to payment</button>
</div>
</form>
</>
);
}
the error is on the useEffect function. In your implementation the function on all change call iteratively on the index the getGrowthAmount function that create a new array each time.
You should call only one time the getGrowthAmount function and in the cycle value the array at the index of the cycle. Then you can update pass the array to be updated.
I have a function to populate certain sections of my site with new content on a regular interval (5000), however my function isn't executing when I run the app?
If I set the interval to a console.log function, it works, but it doesn't seem to be working on my 'updateTestimonial' function. If I manually change the idx, it works fine...
I am just learning so apologies if this is obvious -_-
import coverphoto from './img/bkg/moss2.jpg';
import quotes from './img/quotes.png';
import quotes2 from './img/quotes2.png';
import React, { useState, useEffect } from 'react';
const Home = props =>{
const testimonials = [
{
name: 'Ben Frank',
position: 'CEO',
photo: require('./img/chrisphoto.png'),
text:
"Test"
},
{
name: 'Jill Cha',
position: 'Software Engineer',
photo: require('./img/chrisphoto.png'),
text:
'Testimonial1'
},
{
name: 'Adam Niskanen',
position: 'Data Entry',
photo: require('./img/chrisphoto.png'),
text:
"Testimonial2"
},
];
let idx = 0;
let name = testimonials[idx].name;
let position= testimonials[idx].position;
let photo= testimonials[idx].photo;
let text = testimonials[idx].text;
function updateTestimonial() {
idx++;
if (idx > testimonials.length - 1) {
idx = 0;
}
name = testimonials[idx].name;
position= testimonials[idx].position;
photo= testimonials[idx].photo;
text = testimonials[idx].text;
}
setInterval(updateTestimonial, 5000);
return (
<div className="home_main" style={{
backgroundImage: `url(${coverphoto})`,
backgroundRepeat: 'no-repeat'}}>
<div className="home-testimonial-container"><img className="quotes" src={quotes}/><img className="quotes2" src={quotes2}/><div className='testimonial-entry'>
<img className='testimonial-photo'
src={photo}
></img>
<div className='testimonial-text'>
<h3 className='titlestyle2'>{name}</h3><h3 className='subtitlestyle' style={{fontSize: "10pt"}}>{position}</h3></div>
<div className='testimonial-body-container'><h3 className='bodystyle' style={{fontStyle:"italic"}}>{text}</h3>
</div>
</div></div>
</div>
);
}
export default Home;
You need to call setInterval inside a useEffect hook and don't forget to clear the interval when the component unmounts
check the following article https://upmostly.com/tutorials/setinterval-in-react-components-using-hooks
You can do like this
const [idx, setIdx] = useState(0);
useEffect(() => {
const interval = setInterval(() => setIdx((previousValue) => previousValue
+1), 5000);
return () => {
clearInterval(interval);
};
}, []);
I have an array of images inside refData. I then map them into an array of img objects with RefItem. I then want thouse specifc images to change to the next one in a line - so img 0 becomes 1, and 1 becomes 2 - as written in refbox. I just cannot figure this one out.. I just want to update the individual numbers in each array item?
import React, {useState, useEffect} from 'react'
import refData from './refData'
import RefItem from './refItem'
function ReferencesPP(){
const refItems = refData.map(item => <RefItem key={item.id} pitem={item}/>)
const [refs, setRefs] = useState([0, 1, 2, 3])
useEffect(() => {
const intervalId = setTimeout(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
setRefs(...refs[i], refs[i] = refs[i] + 1)
} else {
setRefs(...refs[i], refs[i] = 0)
}
}
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
return(
<div className="referencespp-container">
<div className="background-container" />
<div className="content-container">
<div id="refbox">
{refItems[refs[0]]}
{refItems[refs[1]]}
{refItems[refs[2]]}
{refItems[refs[3]]}
</div>
</div>
</div>
)
}
export default ReferencesPP
I thought it would be as simple, as writing
const refs = [0, 1, 2, 3]
useEffect(() => {
const intervalId = setInterval(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
refs[i] = refs[i] + 1;
} else {
refs[i] = 0;
}
}
}, 2000);
return () => clearInterval(intervalId);
}, []);
but doing that only updates the const and not the {refItems[refs[0]]} elements?
Have a look at this https://jsfiddle.net/gwbo4pdu/ I think it's what you want to get
React.useEffect(() => {
const intervalId = setTimeout(() => {
const newRefs = [...refs]
const i = newRefs.shift()
newRefs.push(i)
setRefs(newRefs);
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
P.S. you can do just newRefs.push(newRefs.shift())
I am trying to make a simple 'Nonogram'/'Picross' game using React to learn UseContext and UseReducer, but am puzzled as to why my top component (App) is not re-rendering when a value it uses changes. Perhaps I am missing something basic, but I've read through documentation and examples online and can't see why it is not re-rendering.
Expectation: User goes on the application, clicks on the squares to change their value (draw a cross by clicking on the squares), and the text underneath the board reads "Congratulations!", as it is based on the value of 'isComplete'
Problem: As above, but 'Keep trying' remains.
I added a button to see the boardState as defined in the UseReducer function, too.
Code is as follows:
App.js
import './App.css';
import { useReducer } from 'react';
import Table from './Table';
import BoardContext from './BoardContext';
import boardReducer from './BoardReducer';
function App() {
//Puzzle layout
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
//Creating a set of blank arrays to start the game as the userSelection
const generateUserSelection = () => {
const userSelection = [];
puzzleArray.forEach(row => {
let blankRow = [];
row.forEach(square => {
blankRow.push(false)
});
userSelection.push(blankRow);
})
return userSelection;
};
//Initial Context value
const boardInfo = {
puzzleName: "My Puzzle",
puzzleArray: puzzleArray,
userSelection: generateUserSelection(),
isComplete: false
};
const [ boardState, dispatch ] = useReducer(boardReducer, boardInfo)
return (
<BoardContext.Provider value={{board: boardState, dispatch}}>
<div className="App">
<header className="App-header">
<p>
Picross
</p>
<Table />
</header>
<div>
{boardState.isComplete ?
<div>Congratulations!</div>
: <div>Keep trying</div>
}
</div>
<button onClick={() => console.log(boardState)}>boardState</button>
</div>
</BoardContext.Provider>
);
}
export default App;
Table.jsx:
import { useContext, useEffect } from 'react';
import './App.css';
import Square from './Square';
import BoardContext from './BoardContext';
function Table() {
useEffect(() => {console.log('table useEffect')})
const { board } = useContext(BoardContext);
const generateTable = solution => {
const squareLayout = []
for (let i = 0; i < solution.length; i++) {
const squares = []
for (let j = 0; j < solution[i].length; j++) {
squares.push(
<Square
position={{row: i, column: j}}
/>
);
};
squareLayout.push(
<div className="table-row">
{squares}
</div>
);
};
return squareLayout;
};
return (
<div className="grid-container">
{generateTable(board.puzzleArray)}
</div>
);
}
export default Table;
Square.jsx
import { useContext, useState } from 'react';
import './App.css';
import BoardContext from './BoardContext';
function Square(props) {
const { board, dispatch } = useContext(BoardContext)
const [ isSelected, setIsSelected ] = useState(false);
const { position } = props;
const handleToggle = () => {
console.log(board)
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]
dispatch(board);
setIsSelected(!isSelected);
}
return (
<div className={`square ${isSelected ? " selected" : ""}`}
onClick={handleToggle}
>
{position.row}, {position.column}
</div>
);
}
export default Square;
Thanks
Edit: I know for a simple application like this it would be very easy to pass down state through props, but the idea is to practice other hooks, so wanting to avoid it. The ideas I am practicing in this would ideally be extensible to bigger projects in the future.
Edit 2: As requested, here's my BoardReducer.js file:
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete;
return updateInfo;
}
export default boardReducer;
(using JSON.stringify as a cheap way to check matching arrays as it's only a small one for now!)
Issue
You are mutating your state object in a couple places:
const handleToggle = () => {
console.log(board);
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]; // <-- mutation!
dispatch(board);
setIsSelected(!isSelected);
}
And in reducer
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete; // <-- mutation!
return updateInfo; // <-- returning mutated state object
}
Since no new state object is created React doesn't see a state change and doesn't rerender your UI.
Solution
useReducer will typically employ a "redux" pattern where the reducer function consumes the current state and an action to operate on that state, and returns a new state object.
You should dispatch an action that toggles the user selection and checks for a complete board.
Board Reducer
When updating state you should shallow copy any state objects that you are updating into new object references, starting with the entire state object.
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
Create an action creator, which is really just a function that returns an action object.
const togglePosition = position => ({
type: "TOGGLE",
position
});
Then the handleToggle should consume/pass the row and column position in a dispatched action.
const handleToggle = () => dispatch(togglePosition(position));
Simple Demo
Demo Code:
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
const userSelection = Array(3).fill(Array(3).fill(false));
const togglePosition = (row, column) => ({
type: "TOGGLE",
position: { row, column }
});
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
export default function App() {
const [boardState, dispatch] = React.useReducer(boardReducer, {
puzzleArray,
userSelection,
isComplete: false
});
const handleClick = (row, column) => () =>
dispatch(togglePosition(row, column));
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>{boardState.isComplete ? "Congratulations!" : "Keep Trying"}</div>
<div>
{boardState.userSelection.map((row, r) => (
<div key={r}>
{row.map((col, c) => (
<span
key={c}
className={classnames("square", { active: col })}
onClick={handleClick(r, c)}
/>
))}
</div>
))}
</div>
</div>
);
}