Tic tac toe react state not updating - reactjs

I am learning react and i am trying to build the tic tac toe game using the documentation, however using functional components on my own.
This is what I have done till now
app.js
function App() {
return (
<div className="App">
<Game />
</div>
);
}
Game.js
const styles = {
width: "200 px",
margin: "20px auto",
};
function Game() {
const [board, setBoard] = useState(Array(9).fill(""));
const [xIsNext, setXIsNext] = useState(true);
const winner = calculateWinner(board);
const handleClick = (i) => {
const boardCopy = [...board];
if (winner || boardCopy[i]) return;
boardCopy[i] = xIsNext ? "X" : "O";
setBoard(boardCopy);
setXIsNext(!xIsNext);
};
function renderMoves() {
return <button onClick={() => Array(9).fill(null)}> Start Game</button>;
}
return (
<>
<Board onClick={handleClick} squares={board} />
<div style={styles}>
<p>
{winner ? "Winner " + winner : "Next player" + (xIsNext ? "X" : "O")}
{renderMoves()}
</p>
</div>
</>
);
}
Board.js
function Board({ onClick, squares }) {
const style = {
border: "4px solid darkblue",
borderRadius: "10px",
width: "250px",
height: "250px",
margin: "0 auto",
display: "grid",
gridTemplate: "repeat(3, 1fr) / repeat(3, 1fr)",
};
return (
<div style={style}>
{squares.map(
(square, i) => (
console.log(square),
(
<Square
key={i}
value={square}
onClick={() => onClick("dummy value")}
/>
)
)
)}
</div>
);
}
Button.js
function Square({ value, onClick }) {
return (
<button style={style} onClick={onClick}>
{value}
</button>
);
}
At this point in the code the game is functional and user should be able to play the game.
However, in my current code, when you click on Square nothing appears.
I checked in Game.js, the logic for handleClick() is same as the online documentation. These lines handle the logic for showing "X" and "O"
const boardCopy = [...board];
if (winner || boardCopy[i]) return;
boardCopy[i] = xIsNext ? "X" : "O";
setBoard(boardCopy);
setXIsNext(!xIsNext);
it creates a shallow copy of the original board and updates the original board state with the new one.
in Board.js, I put a console.log(square) to see what is being passed here, this shows NULL.
After the state is updated the console.log() show say X or O right?
What am I doing wrong here?
PS - I also one side question,
1)since I am new to this what is the difference of use between writing functions like below, both work perfectly so which should be used when?
function test({i}){
}
and
const test = (i) =>
{
}

here you are not doing nothing onClick, looks like onClick takes a parameter "i" but your passing "dummy value"
<Square
key={i}
value={square}
onClick={() => onClick("dummy value")}
/>
it should be something like
function Board({ handleClick, squares }) {
<Square
key={i}
index={i}
value={square}
handleClick={handleClick}
/>
function Square({ value, index, handleClick}) {
return (
<button style={style} onClick={() => handleClick(index)}>
{value}
</button>
);
}

Related

How to use useState in loop?

I'm trying develop a little app in which on you can select multiple music album, using Next.js.
I display my albums like the image below, and I would like to add a check mark when clicked and hide it when clicked again.
My code looks like that :
import Image from "next/image";
import {Card,CardActionArea} from "#mui/material";
import { container, card } from "../styles/forms.module.css";
import album from "../public/album.json"
export default function Album() {
const albumList = {} ;
function addAlbum(albumId, image){
if ( !(albumId in albumList) ){
albumList[albumId] = true;
//display check on image
}
else{
delete albumList[albumId]
//hide check on image
}
console.log(albumList)
}
return (
<div className={container}>
{Object.keys(album.albums.items).map((image) => (
<Card className={card}>
<CardActionArea onClick={() => addAlbum(album.albums.items[image].id)}>
<Image alt={album.albums.items[image].artists[0].name} width="100%" height="100%" src={album.albums.items[image].images[1].url} />
</CardActionArea>
</Card>
))}
</div>
);
}
I know I should use useState to do so, but how can I use it for each one of my albums?
Sorry if it's a dumb question, I'm new with Hook stuff.
I think there are a few ways to go about this, but here is a way to explain the useState in a way that fits the question. CodeSandbox
For simplicity I made a Card component that knowns if it has been clicked or not and determines wither or not it should show the checkmark. Then if that component is clicked again a clickhandler from the parent is fired. This clickhandle moves the Card into a different state array to be handled.
The main Component:
export default function App() {
const [unselectedCards, setUnselectedCards] = useState([
"Car",
"Truck",
"Van",
"Scooter"
]);
const [selectedCards, setSelectedCards] = useState([]);
const addCard = (title) => {
const temp = unselectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setUnselectedCards(temp);
setSelectedCards([...selectedCards, title]);
};
const removeCard = (title) => {
console.log("title", title);
const temp = selectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setSelectedCards(temp);
setUnselectedCards([...unselectedCards, title]);
};
return (
<div className="App">
<h1>Current Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{unselectedCards.map((title) => (
<Card title={title} onClickHandler={addCard} key={title} />
))}
</div>
<h1>Selected Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{selectedCards.map((title) => (
<Card title={title} onClickHandler={removeCard} key={title} />
))}
</div>
</div>
);
}
The Card Component
export const Card = ({ onClickHandler, title }) => {
const [checked, setChecked] = useState(false);
const handleClickEvent = (onClickHandler, title, checked) => {
if (checked) {
onClickHandler(title);
} else {
setChecked(true);
}
};
return (
<div
style={{
width: "200px",
height: "250px",
background: "blue",
position: "relative"
}}
onClick={() => handleClickEvent(onClickHandler, title, checked)}
>
{checked ? (
<div
id="checkmark"
style={{ position: "absolute", left: "5px", top: "5px" }}
></div>
) : null}
<h3>{title}</h3>
</div>
);
};
I tried to make the useState actions as simple as possible with just a string array to help you see how it is used and then you can apply it to your own system.
You do not need to have a state for each album, you just need to set albumList as a state:
const [albumList, setAlbumList] = setState({});
function addAlbum(albumId, image) {
const newList = {...albumList};
if(!(albumId in albumList)) {
newList[albumId] = true;
} else {
delete albumList[albumId]
}
setAlbumList(newList);
}
And then in your loop you can make a condition to display the check mark or not by checking if the id is in albumList.

Cannot display a number of input fields given a value

I'm trying to display fields based on the value of a props so let's say my props value = 2 then I want to display 2 inputs but I can't manage to get it work.
This is what I tried
const [numberOfFields, setNumberOfFields] = useState(0);
const [loadFields, setloadFields] = useState([]);
const addField = () => {
return loadFields.map((tier) => {
<div>
<p style={{color:'black'}}>Tier {tier + 1}</p>
<InputNumber />
</div>
})
}
const onPropsValueLoaded = (value) => {
let tmp = value
setNumberOfFields(tmp);
if (numberOfFields > 0) {
const generateArrays = Array.from(value).keys()
setloadFields(generateArrays);
} else {
setloadFields([]);
}
}
useEffect(() => {
onPropsValueLoaded(props.numberOfTiers);
}, [])
return (
<>
<Button type="primary" onClick={showModal}>
Buy tickets
</Button>
<Modal
title="Buy ticket"
visible={visible}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<p style={{ color: 'black' }}>{props.numberOfTiers}</p>
{loadFields.length ? (
<div>{addField()}</div>
) : null}
<p style={{ color: 'black' }}>Total price: </p>
</Modal>
</>
);
so here props.NumberOfTiers = 2 so I want 2 input fields to be displayed but right now none are displayed even though loadFields.length is not null
I am displaying this inside a modal (even though I don't think it changes anything).
I am doing this when I load the page that's why I am using the useEffect(), because if I use a field and update this onChange it works nicely.
EDIT:
I changed the onPropsValueLoaded() function
const generateArrays = Array.from({length : tmp}, (v,k) => k)
instead of
const generateArrays = Array.from(value).keys()
There are couple of things you should fix in here,
First, you need to return div in addField function to render the inputs.
Second, you should move your function onPropsValueLoaded inside useEffect or use useCallback to prevent effect change on each render.
Third, your method of creating array using Array.from is not correct syntax which should be Array.from(Array(number).keys()).
So the working code should be , I also made a sample here
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [numberOfFields, setNumberOfFields] = useState(0);
const [loadFields, setloadFields] = useState([]);
const addField = () => {
return loadFields.map((tier) => {
return (
<div key={tier}>
<p style={{ color: "black" }}>Tier {tier + 1}</p>
<input type="text" />
</div>
);
});
};
useEffect(() => {
let tmp = 2; // tier number
setNumberOfFields(tmp);
if (numberOfFields > 0) {
const generateArrays = Array.from(Array(tmp).keys());
setloadFields(generateArrays);
} else {
setloadFields([]);
}
}, [numberOfFields]);
return (
<>
<button type="button">Buy tickets</button>
<p style={{ color: "black" }}>2</p>
{loadFields.length ? <div>{addField()}</div> : null}
<p style={{ color: "black" }}>Total price: </p>
</>
);
}

TypeError: Cannot read property '0' of undefined when building my react app

while building my react app for deployment, I am getting this error
TypeError: Cannot read property '0' of undefined
when I am rending on port3000 I did not see this error but only get it while building the app.
Can anyone assist to resolve this?
import { useState } from "react";
import styles from "./Tabs.module.css"
const Tabs = ({ children}) => {
const [activeTab, setActiveTab] = useState (children [0].props.label);
const handleClick =( e, newActiveTab ) => {
e.preventDefault();
setActiveTab(newActiveTab);
}
return (
<div>
<ul className= {styles.tabs}>
{children.map ((tab) => {
const label = tab.props.label;
return (
<li
className= {label == activeTab ? styles.current : ""}
key= {label}
>
<a href="#" onClick={(e) => handleClick (e, label)}>{label}
</a>
</li>
)
})}
</ul>
{children.map ((tabcontent1) => {
if (tabcontent1.props.label == activeTab)
return (
<div key= {tabcontent1.props.label} className= {styles.content}>{tabcontent1.props.children}
</div>
);
})}
</div>
);
}
export default Tabs ;
In next js, when you don't put export const getServerSideProps = () => {} in your page then that page is automatically subjected to static side rendering. On development mode, you may see a lightening symbol on bottom-right. Anyway you can read the docs on data-fetching on nextjs. However, your issue on this situation can be easily fixed by setting the children through useEffect.
// handle null on your active tab render function
const [activeTab, setActiveTab] = useState(null);
useEffect(() => {
if(children.length)
children[0].props.label
}, [children])
Another Code Sample:
*A simple change in code structure and the way you are trying to do. It's on react but kind of same in next as well *
import React from "react";
const Tabs = ({ tabsData }) => {
const [activeTabIndex, setActiveTabIndex] = React.useState(0);
const switchTabs = (index) => setActiveTabIndex(index);
return (
<div style={{ display: "flex", gap: 20, cursor: "pointer" }}>
{/* Here active tab is given a green color and non actives grey */}
{tabsData.map((x, i) => (
<div
key={i}
style={{ color: activeTabIndex === i ? "green" : "#bbb" }}
onClick={() => switchTabs(i)}
>
{x.label}
</div>
))}
{/* Show Active Tab Content */}
{tabsData[activeTabIndex].content}
</div>
);
};
export default function App() {
// You can place it inside tabs also in this case
// but lets say you have some states on this component
const tabsData = React.useMemo(() => {
return [
// content can be any component or React Element
{ label: "Profile", content: <p>Verify all Input</p> },
{ label: "Settings", content: <p>Settings Input</p> },
{ label: "Info", content: <p>INput info</p> }
];
}, []);
return (
<div className="App">
<Tabs tabsData={tabsData} />
</div>
);
}
and here is also a example sandbox https://codesandbox.io/s/serverless-night-ufqr5?file=/src/App.js:0-1219

Render JSX element in react-contenteditable

In react-contenteditable, the html attributes only accepts string, how can I manage to add JSX element with eventlistener with in the string.
Sandbox
import ContentEditable from "react-contenteditable";
import "./styles.css";
const text = "I want to order cheese chicken pizza.";
const Elems = {
cheese: (
<span style={{ color: "red" }} onClick={() => alert("clicked cheese span")}>
cheese
</span>
),
chicken: (
<span
style={{ color: "red" }}
onClick={() => alert("clicked chicken span")}
>
chicken
</span>
)
};
export default function App() {
const swapText = () => {
const text_array = text.split(" ");
console.log(text_array);
const a = text_array.map((item) => {
if (item in Elems) item = Elems[item];
else item += " ";
return item;
});
return a;
};
return (
<div className="App">
<h2>React contenteditable</h2>
<ContentEditable html={swapText()} />
</div>
);
}
You can convert react elements to markup using ReactDOMServer.renderToStaticMarkup(element). This would help with the styles, but not with the click handler:
if (item in Elems) item = renderToStaticMarkup(Elems[item]);
For the items to be clickable, you'll need to pass an onClick handler to <ContentEditable> component (or a parent of it):
<ContentEditable onClick={handleClick} html={swapText()} />
You would also need to identify the clickable elements. In this example, I've data-action tags to both of them:
const Elems = {
cheese: (
<span style={{ color: 'red' }} data-action="cheese">
cheese
</span>
),
chicken: (
<span style={{ color: 'red' }} data-action="chicken">
chicken
</span>
)
};
The click handler searches the event target or a parent that has the data-action tag using Element.closest(), if it finds one it acts on the tags value:
const handleClick = (e) => {
const target = e.target.closest('[data-action]');
if (!target) return;
const action = target.dataset.action;
alert(action);
};
Working example - sandbox

intercommunication between pair of elements created in array of React.createElement

Let's say I have 2 react elements: componentSender and componentReceiver. That need to be generated in a loop N times.
The special thing they have is that every time someone click in one componentSender, a prop will change in the respective componentReceiver.
This pair of components could be as simple as:
function ComponentReceiver(props) {
return (
<div>{`Listening to "Sender ${props.indexVar}" and it last received: ${props.showVar}`}</div>
);
}
function ComponentSender(props) {
return (
<input type="button" onClick={() => {props.onChangeValue(props.indexVar);}}
value={`SENDER for ${props.indexVar}> `}
/>
);
}
I am using React.createElement in a loop and creating the pairs, you can see it here:
https://codepen.io/danieljaguiar/pen/bGVJbGw?editors=1111
The big problem in my demo is that, when I change the state in the parent (APP), the child components don't re-render.
You have to fix up few things:
try not to store jsx in state. Iterate and render directly in render.
in handleChangeValue function, the show state reference is not changed at all and hence the component is not re-rendered. Make sure to take a copy of show (use spread operator) and then update state.
remove unnecessary code in useEffect and
Working & simplified copy of your code is here in the codesandbox
Code Snippet with fixes
function ComponentReceiver(props) {
return (
<div>{`Listening to "Sender ${props.indexVar}" and I received: ${
props.showVar
}`}</div>
);
}
function ComponentSender(props) {
return (
<input
type="button"
onClick={() => {
props.onChangeValue(props.indexVar);
}}
value={`SENDER for ${props.indexVar} ----------------------> `}
/>
);
}
export default function App() {
const [show, SetShow] = React.useState([]);
const [pageElements, setPageElements] = React.useState([]);
const handleChangeValue = val => {
const updatedShow = [...show];
updatedShow[val] = !updatedShow[val];
SetShow(updatedShow);
};
React.useEffect(() => {
let index = 0;
let elements = [];
while (index < 5) {
show[index] = true;
SetShow([...show]);
elements.push(
<div key={index} style={{ display: "flex", margin: "20px" }}>
<ComponentSender
key={index + "s"}
indexVar={index}
onChangeValue={handleChangeValue}
/>
<ComponentReceiver
key={index + "R"}
indexVar={index}
showVar={show[index]}
/>
</div>
);
index++;
SetShow([...show]);
}
setPageElements(elements);
}, []);
return (
<div>
{[...Array(5).keys()].map((_, index) => {
return (
<div key={index} style={{ display: "flex", margin: "20px" }}>
<ComponentSender
key={index + "s"}
indexVar={index}
onChangeValue={handleChangeValue}
/>
<ComponentReceiver
key={index + "R"}
indexVar={index}
showVar={show[index]}
/>
</div>
);
})}
</div>
);
}

Resources