I'm making a Nextjs app, and on one page I create an array of numbers that is shuffled using a random number. The problem is that every time the state changes, the component gets rendered so the array gets re-shuffled. I need the original shuffle to stay consistent. Here's the relevant code:
export default function Game() {
const { query } = useRouter();
const [cardsFlipped, setCardsFlipped] = useState(
Array(query.tileNumber).fill(false)
);
let counter = query.tileNumber / 2;
let iconSet = [...Array(counter).keys()];
while (counter > 0) {
const index = Math.floor(Math.random() * iconSet.length);
tiles.push(iconSet.splice(index, 1));
counter--;
}
const tileOrder = tiles.concat(tiles).sort((a, b) => 0.5 - Math.random());
const handleFlip = (index) => {
if (cardsFlipped[index] === false) {
setCardsFlipped((prev) =>
prev.map((el, i) => {
if (i === index) {
return true;
}
return el;
})
);
setTimeout(() => {
setCardsFlipped((prev) =>
prev.map((el, i) => {
if (i === index) {
return false;
}
return el;
})
);
}, query.tileTransition * 1000);
}
};
let cards = tileOrder.map((e, i) => (
<ReactCardFlip
isFlipped={cardsFlipped[i]}
flipDirection="horizontal"
key={"card" + i + "-" + e}
>
<Card
iconSet={2}
index={50}
callback={() => {
handleFlip(i);
}}
/>
<Card iconSet={parseInt(query.icons)} index={e} callback={handleFlip} />
</ReactCardFlip>
));
return (<div>{cards}</div>);
}
I thought of converting it into a class, and having a constructor, but then I get an error that useRouter can't be used in classes.
You just need to wrap your logic in a useEffect, something like this:
export default function Test() {
const { query } = useRouter();
const [cardsFlipped, setCardsFlipped] = useState(
Array(query.tileNumber).fill(false)
);
const [tileOrder, setTileOrder] = useState([]);
useEffect(() => {
let counter = query.tileNumber / 2;
let iconSet = [...Array(counter).keys()];
while (counter > 0) {
const index = Math.floor(Math.random() * iconSet.length);
tiles.push(iconSet.splice(index, 1));
counter--;
}
setTileOrder(tiles.concat(tiles).sort((a, b) => 0.5 - Math.random()));
}, []);
const handleFlip = index => {
if (cardsFlipped[index] === false) {
setCardsFlipped(prev =>
prev.map((el, i) => {
if (i === index) {
return true;
}
return el;
})
);
setTimeout(() => {
setCardsFlipped(prev =>
prev.map((el, i) => {
if (i === index) {
return false;
}
return el;
})
);
}, query.tileTransition * 1000);
}
};
let cards = tileOrder.map((e, i) => (
<ReactCardFlip
isFlipped={cardsFlipped[i]}
flipDirection="horizontal"
key={'card' + i + '-' + e}
>
<Card
iconSet={2}
index={50}
callback={() => {
handleFlip(i);
}}
/>
<Card iconSet={parseInt(query.icons)} index={e} callback={handleFlip} />
</ReactCardFlip>
));
return <div>{cards}</div>;
}
Related
I have created a Wordle game, I am trying to get the values from the inputs by line. Despite many attempts, a value is received only after I enter the next letter in the next input and an alert message pops up according to the new letter from the beginning of the next line appears.
import React, { ChangeEvent, useEffect,useRef, useState } from "react";
interface Props {
word: string;
numberOfLines: number;
numberOfInputs: number;
}
const Wordle = ({ word, numberOfLines, numberOfInputs }: Props) => {
const [inputs, setInputs] = useState<string[][]>(
Array(numberOfLines)
.fill(null)
.map(() => Array(numberOfInputs).fill(""))
);
const fullWord = useRef("");
const [currentRow, setCurrentRow] = useState(0);
const [currentCol, setCurrentCol] = useState(0);
const [color, setColor] = useState<string[][]>(
Array(numberOfLines)
.fill(null)
.map(() => Array(numberOfInputs).fill("white"))
);
const handleChange = (
e: ChangeEvent<HTMLInputElement>,
row: number,
col: number
) => {
const updatedInputs = inputs.map((inputRow, i) => {
if (i === row) {
return inputRow.map((input, j) => (j === col ? e.target.value : input));
}
return inputRow;
});
setInputs(updatedInputs);
if (col === numberOfInputs - 1) {
setCurrentRow(row + 1);
setCurrentCol(0);
} else {
setCurrentRow(row);
setCurrentCol(col+1);
}
const updatedColor = color.map((colorRow, i) => {
if (i === row) {
return colorRow.map((color, j) => {
if (j === col) {
if (word.includes(e.target.value)) {
if (word[j] === e.target.value) {
return "green";
}
return "brown";
}
return "white";
}
return color;
});
}
return colorRow;
});
setColor(updatedColor);
if(fullWord.current.length < word.length)
{fullWord.current = fullWord.current + e.target.value;
document
.getElementsByTagName("input")[row * numberOfInputs + col + 1].focus();
}
else if (fullWord.current.length === word.length) {
if (fullWord.current == word) {
alert("Success!");
fullWord.current="";
document
.getElementsByTagName("input")[row * numberOfInputs + col].focus();
} else {
fullWord.current="";
alert("Fail!");
}
}
}
return (
<div>
{inputs.map((inputRow, i) => (
<div key={i} className="word">
{inputRow.map((input, j) => (
<input
key={j}
type="text"
value={input}
maxLength={1}
onChange={(e) => handleChange(e, i, j)}
style={{ backgroundColor: color[i][j] }}
/>
))}
</div>
))}
{/* <Keyboard onClick={handleClick} /> */}
</div>
);
};
export default Wordle;
I want to get the value before the focus moves to the next input.
Thanks
So I'm trying to create an infinite scroll component that will display if the component is scrolling left, right, or not at all. I'm running into alot of issues using hooks. Ive tried combining this https://dirask.com/posts/React-scroll-stop-event-DkBe01 with this https://codesandbox.io/s/icy-river-wr0uk?file=/src/App.js but could not get it functioning. Lastly, any ideas why it smoothly transitions when I scroll right but it stops upon scrolling left? Thank you guys for your help, this is my first post and I appreciate any help from the community!
import React, { Component, useEffect, useRef, useReducer, useState } from 'react'
import Card from './Card'
import './skills.scss'
import myImage from './Images/editIcon.png'
function SkillsFunction() {
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{disableScroll: false, scrollWidth: 0, scrollPos: 1, clonesWidth: 0}
);
const scrollContainerRefy = useRef();
function reCalc() {
let scrollPos = scrollPos;
let scrollWidth = scrollContainerRefy.current.clientWidth;
let clonesWidth = getClonesWidth();
if (scrollPos <= 0) {
scrollPos = 1;
}
setState({
scrollPos: scrollPos,
scrollWidth: scrollWidth,
clonesWidth: clonesWidth,
});
};
// function sideScroll(element,direction,speed,distance,step) {
// let scrollAmount = 0;
// var slideTimer = setInterval(() => {
// if(direction == 'left'){
// element.scrollLeft -= step;
// } else {
// element.scrollLeft += step;
// }
// scrollAmount += step;
// if(scrollAmount >= distance){
// window.clearInterval(slideTimer);
// }
// }, speed);
// };
function handleScroll(e) {
const container = e.currentTarget;
const scrollWidth = container.scrollWidth;
const clonesWidth = getClonesWidth();
let scrollPos = container.scrollLeft;
let scrollPosAdd;
container.clientWidth > clonesWidth ? scrollPosAdd = container.clientWidth : scrollPosAdd = clonesWidth;
if (!state.disableScroll) {
if (scrollPos + scrollPosAdd >= scrollWidth) {
setScroll(
// The math floor value helps smooth out the scroll jump,
// I don't know why that particular value works, but it does
// Same goes for the other setScroll call below
container, 1
// + Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
} else if (scrollPos <= 0) {
setScroll(
container, scrollWidth - clonesWidth
// - Math.floor(scrollPosAdd/50)
);
setState({
disableScroll: true,
});
}
}
setState({
scrollWidth: container.scrollWidth,
scrollPos: container.scrollLeft,
});
} ;
function getClonesWidth() {
const clones = document.getElementsByClassName('is-clone');
let clonesWidth = 0;
for (let i = 0; i < clones.length; i++) {
clonesWidth = clonesWidth + clones[i].clientWidth;
}
return clonesWidth;
};
function setScroll(element, pos) {
element.scrollLeft = pos;
setState({
scrollPos: element.scrollLeft,
});
};
function scrollNext(e) {
const container = e.currentTarget.previousSibling;
const element = container;
const direction = 'right';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
function scrollPrev(e) {
const container = e.currentTarget.nextSibling;
const element = container;
const direction = 'left';
const speed = 10;
const distance = 272;
const step = 10;
let scrollAmount = 0;
var slideTimer = setInterval(() => {
if(direction == 'left'){
element.scrollLeft -= step;
} else {
element.scrollLeft += step;
}
scrollAmount += step;
if(scrollAmount >= distance){
window.clearInterval(slideTimer);
}
}, speed);
};
// function componentDidUpdate(prevProps, prevState) {
// };
// function componentDidMount() {
// window.addEventListener('resize', reCalc);
// };
// function componentWillUnmount() {
// window.removeEventListener('resize', reCalc);
// };
useEffect(() => {
// Update the document title using the browser API
if (state.disableScroll) {
window.setTimeout(function() {
setState({
disableScroll: false,
});
}, 40)
}
});
const [scrollDir, setScrollDir] = useState("scrolling left");
const containerRef = useRef();
useEffect(() => {
console.log("olay");
const threshold = 0;
let lastScrollX = window.scrollX;
let ticking = false;
const updateScrollDir = () => {
const scrollX = window.scrollX;
if (Math.abs(scrollX - lastScrollX) < threshold) {
ticking = false;
return;
}
setScrollDir(
scrollX > lastScrollX ? "scrolling right" : "scrolling left"
);
lastScrollX = scrollX > 0 ? scrollX : 0;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(updateScrollDir);
ticking = true;
}
};
window.addEventListener("scroll", onScroll);
console.log(scrollDir);
return () => window.removeEventListener("scroll", onScroll);
}, [scrollDir]);
return(
<>
<div className="card-container">
<div className="scroll scroll-prev" onClick={scrollPrev}>
<i className="fas fa-chevron-left"></i>
</div>
<div ref={containerRef} className="scrolling-wrapper" onScroll={handleScroll}>
<Card title={'Card Number 7'} classes={""}/>
<Card title={'Card Number 1'} classes={""}/>
<Card title={'Card Number 2'}/>
<Card title={'Card Number 3'}/>
<Card title={'Card Number 4'}/>
<Card title={'Card Number 5'}/>
<Card title={'Card Number 6'} image={myImage}/>
<Card title={'Card Number 7'} classes={"is-clone is-start"}/>
<Card title={'Card Number 1'} classes={"is-clone"}/>
<Card title={'Card Number 2'} classes={"is-clone"}/>
<Card title={'Card Number 3'} classes={"is-clone"}/>
<Card title={'Card Number 4'} classes={"is-clone"}/>
<Card title={'Card Number 5'} classes={"is-clone"}/>
<Card title={'Card Number 6'} classes={"is-clone"}/>
</div>
<div className="scroll scroll-next" onClick={scrollNext}>
<i className="fas fa-chevron-right"></i>
</div>
</div>
<div>Scrolling </div>
</>
)
}
export default SkillsFunction
For this project I am currently working on, I need to highlight the button that was clicked on each layer/row. However, the way I have right now it highlights every button that was clicked.
I need something like this:
correct highlighted path
But then when I click on the same row, it does not remove the highlight from the button that I pressed before. How can I update and reset the state of the previous button that was clicked? I tried to use the useRef hook for this but I haven't been successful so far.
wrong highlighted path
EDIT: Added code
This is the code that I have for the component of each row in the website:
function StackRow({ data, partition, level, index, onClick, getInfo, isApp }) {
const classes = useStyles({ level: level });
const rowRef = useRef();
const handleSelectedButtons = (flag, setFlag, btnRef) => {
console.log(rowRef);
};
return (
<Card
key={partition + '_' + index + '_' + level}
className={classes.card}
id={level}
ref={rowRef}
>
{data.map((field) => {
return (
<StackItem
key={partition + '_' + field[0] + '_' + level}
data={field[0]}
info={field[1]}
level={level}
onClick={onClick}
getInfo={getInfo}
isApp={isApp}
handleSelectedButtons={handleSelectedButtons}
rowRef={rowRef}
/>
);
})}
</Card>
);
}
And this is the code I have for each button of the row:
function StackItem({
data,
info,
level,
onClick,
getInfo,
isApp,
handleSelectedButtons,
}) {
const [flag, setFlag] = useState(false);
const btnRef = useRef();
const styleProps = {
backgroundColor: flag ? '#06d6a0' : level % 2 === 0 ? '#22223b' : '#335c67',
};
const classes = useStyles(styleProps);
return (
<Button
ref={btnRef}
isselected={flag.toString()}
key={data}
className={classes.button}
variant="outlined"
onClick={(event) => {
onClick(event, setFlag, btnRef);
handleSelectedButtons(flag, setFlag, btnRef);
getInfo(info, level, isApp);
}}
disableElevation={true}
>
{data}
</Button>
);
}
There are some useless variables and states there because I have been trying all sort of stuff to do this.
EDIT: Added data sample & project structure
Data looks like:
{
application: {
cmake: {
info: str,
versions: {
version_no: {
application: {...}
}
}
},
gcc: {...},
git: {...},
intel: {...},
.
.
.
}
}
The structure of the project is like:
App
L Stack
L StackRow
L StackItem
Where App is the entire application, Stack is the container for everything in the images apart from the search box, StackRow matches one row of the Stack, and StackItem is one item/button from the StackRow.
EDIT: Added Stack component
function Stack({ data, partition, getInfo }) {
const [level, setLevel] = useState(0);
const [cards, setCards] = useState([]);
const [isApp, setIsApp] = useState(true);
const [selected, setSelected] = useState([]);
const [prevLevel, setPrevLevel] = useState(-1);
const cardsRef = useRef();
const handleClick = (event, setFlag, btnRef) => {
let rows = cardsRef.current.childNodes;
let currBtn = event.target.innerText;
let curr;
for (let i = 0; i < rows.length; i++) {
let rowItems = rows[i].childNodes;
for (let j = 0; j < rowItems.length; j++) {
if (currBtn === rowItems[j].textContent) {
curr = rowItems[j].parentElement;
}
}
}
let id;
for (let i = 0; i < rows.length; i++) {
if (curr.textContent === rows[i].textContent) {
id = i;
}
}
if (level === id) {
if (id % 2 === 0) {
setIsApp(true);
if (selected.length === 0) {
setSelected([...selected, data[currBtn].versions]);
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(level + 1);
} else {
let newSelected = selected.slice(0, id);
if (id % 2 === 0) {
setIsApp(true);
if (newSelected.length === 0) {
setSelected([...newSelected, data[currBtn].versions]);
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(id + 1);
}
setFlag(true);
};
useEffect(() => {
let fields = [];
let lastSelected = selected[selected.length - 1];
if (level % 2 !== 0) {
fields = Object.keys(lastSelected).map((key) => {
let path = lastSelected[key].path;
let module = lastSelected[key].module_name;
let info = 'module: ' + module + ' path: ' + path;
return [key, info];
});
} else {
if (selected.length !== 0)
fields = Object.keys(lastSelected).map((key) => {
let info = lastSelected[key].info;
return [key, info];
});
}
if (fields.length > 0) {
if (level > prevLevel) {
setCards((prevState) => [...prevState, fields]);
} else {
setCards((prevState) => [
...prevState.slice(0, selected.length),
fields,
]);
}
}
}, [selected, level, prevLevel]);
useEffect(() => {
let fields = Object.keys(data).map((key) => {
let info = data[key].info;
return [key, info];
});
setCards([fields]);
setLevel(0);
}, [data]);
useEffect(() => {
setLevel(0);
setPrevLevel(-1);
setSelected([]);
}, [partition]);
if (cards) {
return (
<div ref={cardsRef}>
{cards.map((card, index) => (
<StackRow
data={card}
partition={partition}
level={index}
index={cards.indexOf(card)}
onClick={handleClick}
getInfo={getInfo}
isApp={isApp}
/>
))}
</div>
);
} else return null;
}
EDIT: Added data sample
{
cmake: {
info: "A cross-platform, open-source build system. CMake is a family of tools designed to build, test and package software.",
versions: {
"3.17.3": {
child: {},
module_name: "cmake/3.17.3",
path: "/opt/apps/nfs/spack/var/spack/environments/matador/modules/linux-centos8-x86_64/Core/cmake/3.17.3.lua",
version_no: "3.17.3"
}
}
},
gcc: {
info: "...",
versions: {
"8.4.0": {
child: {
cmake: {...},
cuda: {...},
cudnn: {...},
openmpi: {...},
.
.
.
},
module_name: "...",
path: "...",
version_no: "..."
}
"9.3.0": {...},
"10.1.0": {...}
}
}
}
Following suggestions from You (StackOverflow), I have rewritten my app to [several components]https://codesandbox.io/s/points-scored-forked-brnni?file=/src/components/ScoredPointsList.js:0-561
Now when I run yarn start, I get a reference error. After googling, I have tried with "everything" including:
changed from npm to yarn, changed in package.json ... but nothing seems to help.
How can I change the code to make it work?
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers = [...players].sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
const onePointHandler = () => {
// eslint-disable-next-linex
const players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit ? {...player, totPoints: player.totPoints + 1} : player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1,
};
setPlayers([...players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber('');
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
Thanks in advance
Regards
Peter
You are trying to modify players variable before it is being initialized , you can useMemo(which runs only if players value change) and modify sortPlayers after it is available and also you were trying to initalize players again that was causing issue
const sortedPlayers =useMemo(()=>{
return players.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
Full Code:
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
import {useMemo} from 'react'
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers =useMemo(()=>{
return players?.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
const onePointHandler = () => {
const _players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = _players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit
? { ...player, totPoints: player.totPoints + 1 }
: player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1
};
setPlayers([..._players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber("");
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
refer sandbox:
i have this code basically a kind of quiz app. I want to do rest timer when user click on next or previous button. Need help for this. We can do that by creating only single component and create one of state as reset but in that case every second main page get re-render.
const CalcTimeLeft = () => {
const [time, setTime] = useState(300000);
useEffect(() => {
setTimeout(() => {
setTime(time - 1000);
}, 1000);
}, [time]);
const timeLeft = {
minutes: Math.floor(time / 60 / 1000) % 60,
seconds: Math.floor(time / 1000) % 60
};
return <div>{`${timeLeft.minutes}:${timeLeft.seconds}`}</div>;
};
export default function App() {
const [currPage, setCurrpage] = useState(1);
const question = [
];
const handleNext = () => {
setCurrpage(currPage + 1);
};
const handlePrev = () => {
setCurrpage(currPage - 1);
};
let item_per_page = 1;
let total_page = Math.floor(question.length / item_per_page);
const data = question.splice((currPage - 1) * item_per_page, item_per_page);
return (
<div className="App">
{data.map((data) => {
return <div>{data.question}</div>;
})}
<CalcTimeLeft />
{currPage !== total_page && <button onClick={handleNext}>Next</button>}
{currPage !== 1 && (
<button onClick={() => setCurrpage(handlePrev)}>Previous</button>
)}
</div>
);
}
You can bubble the reset event in the CalcTimeLeft component.
const CalcTimeLeft = () => {
const [time, setTime] = useState(300000);
const resetTime = () => {
setTime(300000);
};
useEffect(() => {
setTimeout(() => {
setTime(time - 1000);
}, 1000);
}, [time]);
return { resetTime };
};
export default function App() {
const [currPage, setCurrpage] = useState(1);
const question = [];
const { resetTime } = CalcTimeLeft();
const handleNext = () => {
resetTime;
setCurrpage(currPage + 1);
};
const handlePrev = () => {
resetTime;
setCurrpage(currPage - 1);
};
const timeLeft = {
minutes: Math.floor(time / 60 / 1000) % 60,
seconds: Math.floor(time / 1000) % 60,
};
let item_per_page = 1;
let total_page = Math.floor(question.length / item_per_page);
const data = question.splice((currPage - 1) * item_per_page, item_per_page);
return (
<div className="App">
{data.map((data) => {
return <div>{data.question}</div>;
})}
<div>{`${timeLeft.minutes}:${timeLeft.seconds}`}</div>
{currPage !== total_page && <button onClick={handleNext()}>Next</button>}
{currPage !== 1 && <button onClick={handlePrev()}>Previous</button>}
</div>
);
}