I meet problem, when i use componentDidMount(), componentDidMount() use to show Tooltip when use function _getContentTooltip() then the problem show the error common.js:444 RangeError: Maximum call stack size exceeded
import ZCommon from 'utils/common';
import React from 'react';
import ReactDOM from 'react-dom';
class TooltipUtil extends React.Component {
constructor(props) {
super(props);
this.state = {
guidesContent: [],
zguides: [],
current: 1,
hidden: 0
};
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.state.current !== nextState.current ||
this.state.zguides !== nextState.zguides
);
}
_storageData() {
this.state.guidesContent = [{
"id":"1",
"description": "Đây là title của người chat or group",
"name":"tabmsg.searchicon",
"title": "abc",
}, {
"id":"2",
"name":"tabmsg.creategroup",
"description": "Bạn click vào đây để tạo nhóm",
"title": "Xưu nhân",
}, {
"id":"3",
"name":"csc.header.search",
"description": "Đây là khung để nhập nội dung chat",
"title": "abc",
}];
this.setState({
guidesContent: this.state.guidesContent.sort(function(a,b){ return a.id > b.id } )
});
return this.state.guidesContent;
}
_getContentTooltip(){
// this.serverRequest.abort();
let current= this.state.current;
let _guides = this._storageData();
let ele = document.querySelectorAll(".tooltip");
for (var i = 0; i < ele.length; i++) {
var no = ele[i].getAttribute('data-tooltip');
let Tcontent = Object.keys(_guides).filter(function(key) {
if(_guides[key].name == no){
if(_guides[key].id == current){
return key;
}
}
});
this.setState({
zguides: this.state.guidesContent[Tcontent]
})
}
}
componentDidMount(){
this._getContentTooltip();
}
componentDidUpdate(){
this.componentDidMount();
}
_handlerClickClose() {
let _guides = this._storageData();
if(this.state.current <= _guides.length ){
this.setState({
current: this.state.current + 1
});
}
}
render() {
let guides = null;
let obj = this.state.zguides;
let show = this.state.zguides != undefined ? "show" : ' ';
console.log(this.state.zguides);
guides = (
<div className={'guide ' + show } style={{ width:'200px',left:'0'}}>
<div className="guide-content flx">
<div href="#" className="fa fa-close" onClick= {this._handlerClickClose.bind(this)}></div>
<h4>{this.state.zguides['title']}</h4>
<p>{this.state.zguides['description']}</p>
</div>
</div>
);
return guides;
}
_handlerClickClose() {
let _guides = this._storageData();
if(this.state.current <= _guides.length ){
this.setState({
current: this.state.current + 1
});
}
}
}
export default TooltipUtil;
In _getContentTooltip() function you are changing the state.which causes the component to update so componentDidUpdate() function runs ,which also calling again componentDidMount(). that function again calls getContentTooltip().so comment the below line
componentDidUpdate(){
//this.componentDidMount();
}
Fist of all you should not force the lifecycle functions to call forcefully. Now the Maximum Stack Size errors occurs because _getContentTooltip is setting state and hence hence will trigger a rerender resulting in componentDidUpdate lifecycle function being called where you again call componentDidMount thus going in an endless loop. You would say that you have checked whether to previous and current state values are equal in shouldComponentUpdate but comparing arrays like this.state.zguides !== nextState.zguides will return true.
See this answer:
How to compare arrays in JavaScript?
If you want to trigger _getContentTooltip periodically, call it in a setInterval function in componentDidMount
class TooltipUtil extends React.Component {
constructor(props) {
super(props);
this.state = {
guidesContent: [],
zguides: [],
current: 1,
hidden: 0
};
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.state.current !== nextState.current ||
!this.state.zguides.equals(nextState.zguides)
);
}
_storageData() {
this.state.guidesContent = [{
"id":"1",
"description": "Đây là title của người chat or group",
"name":"tabmsg.searchicon",
"title": "abc",
}, {
"id":"2",
"name":"tabmsg.creategroup",
"description": "Bạn click vào đây để tạo nhóm",
"title": "Xưu nhân",
}, {
"id":"3",
"name":"csc.header.search",
"description": "Đây là khung để nhập nội dung chat",
"title": "abc",
}];
this.setState({
guidesContent: this.state.guidesContent.sort(function(a,b){ return a.id > b.id } )
});
return this.state.guidesContent;
}
_getContentTooltip(){
// this.serverRequest.abort();
let current= this.state.current;
let _guides = this._storageData();
let ele = document.querySelectorAll(".tooltip");
for (var i = 0; i < ele.length; i++) {
var no = ele[i].getAttribute('data-tooltip');
let Tcontent = Object.keys(_guides).filter(function(key) {
if(_guides[key].name == no){
if(_guides[key].id == current){
return key;
}
}
});
this.setState({
zguides: this.state.guidesContent[Tcontent]
})
}
}
componentDidMount(){
setInterval(() => {
this._getContentTooltip();
}, 1000)
}
_handlerClickClose() {
let _guides = this._storageData();
if(this.state.current <= _guides.length ){
this.setState({
current: this.state.current + 1
});
}
}
render() {
let guides = null;
let obj = this.state.zguides;
let show = this.state.zguides != undefined ? "show" : ' ';
console.log(this.state.zguides);
guides = (
<div className={'guide ' + show } style={{ width:'200px',left:'0'}}>
<div className="guide-content flx">
<div href="#" className="fa fa-close" onClick= {this._handlerClickClose.bind(this)}></div>
<h4>{this.state.zguides['title']}</h4>
<p>{this.state.zguides['description']}</p>
</div>
</div>
);
return guides;
}
_handlerClickClose() {
let _guides = this._storageData();
if(this.state.current <= _guides.length ){
this.setState({
current: this.state.current + 1
});
}
}
}
export default TooltipUtil;
Related
I am working on a quiz and now I would like to show different messages to the user depending on what score they have. This code is working but I when the score is 0 no new state is set.
It seems to have something to do with that prevProps.score is only triggered when you have answered something correctly. Is there some other conditional I could use instead maybe?
Below is all code in current state:
class App extends React.Component {
constructor(props) {
super(props);
// Make it somewhat harder for cheaters to inspect the correct answer.
document.getElementById('quiz').setAttribute('data-props', '');
const data = JSON.parse(this.props.quiz);
this.state = {
data: data,
nr: 0,
total: data.length,
showButton: false,
questionAnswered: false,
score: 0,
popUpClass: 'popup-visible',
quizVisible: false,
id: data[0].id,
question: data[0].question,
answers: [
data[0].answers[0],
data[0].answers[1],
data[0].answers[2],
data[0].answers[3]
],
correct: data[0].correct
}
this.nextQuestion = this.nextQuestion.bind(this);
this.handleShowButton = this.handleShowButton.bind(this);
this.handleStartQuiz = this.handleStartQuiz.bind(this);
this.handleIncreaseScore = this.handleIncreaseScore.bind(this);
}
pushData(nr) {
const data = this.state.data;
this.setState({
question: data[nr].question,
answers: [data[nr].answers[0], data[nr].answers[1], data[nr].answers[2], data[nr].answers[3] ],
correct: data[nr].correct,
nr: this.state.nr + 1
});
}
nextQuestion() {
let { nr, total} = this.state;
if(nr === total){
this.setState({
popUpClass: 'popup-visible',
quizVisible: false
});
} else {
this.pushData(nr);
this.setState({
showButton: false,
questionAnswered: false,
quizVisible: true
});
}
}
handleShowButton() {
this.setState({
showButton: true,
questionAnswered: true
});
}
handleStartQuiz() {
this.setState({
popUpClass: 'popup-hidden',
quizVisible: true,
nr: 1
});
}
handleIncreaseScore() {
this.setState({
score: this.state.score + 1
});
}
render() {
let { nr, total, id, question, answers, correct, showButton, questionAnswered, popUpClass, quizVisible, score} = this.state;
return (
<div className="app-container">
<Popup className={popUpClass} score={score} total={total} startQuiz={this.handleStartQuiz} key={nr} />
{quizVisible ?
(
<div key={question} className="quiz">
<div className="quiz-box">
<span className="question-total">Fråga {nr} av {total}</span>
<h2 className="question">{question}</h2>
<Answers
key={id}
answers={answers}
correct={correct}
showButton={this.handleShowButton}
isAnswered={questionAnswered}
increaseScore={this.handleIncreaseScore} />
</div>
<div id="submit">
{showButton ? <button className="fancy-btn" onClick={this.nextQuestion} id={nr === total ? 'finishQuiz' : null}>{nr === total ? 'Slutför quiz' : 'Nästa fråga'}</button> : null}
</div>
</div>
) : null}
</div>
);
}
};
class Popup extends React.Component {
constructor(props) {
super(props);
this.state = {
title: 'Quiz',
showStartButton: true
};
this.startQuizHandle = this.startQuizHandle.bind(this);
}
startQuizHandle() {
this.props.startQuiz();
}
componentDidUpdate(prevProps) {
let total = this.props.total;
let highScore = total - 2;
let halfScore = total / 2;
if (this.props.score !== prevProps.score) {
if (this.props.score >= highScore) {
this.setState({
title: 'You are an expert!',
})
} else if (this.props.score >= halfScore && this.props.score <= highScore) {
this.setState({
title: 'You are pretty good at this!'
})
}
else if (this.props.score < halfScore && this.props.score > 0) {
console.log('score less than half');
this.setState({
title: 'You need some practice.'
})
}
else {
this.setState({
title: 'You did not do too well.',
})
}
}
}
createMarkup(text) {
return {__html: text};
}
render() {
let { title, intro, text, showStartButton } = this.state;
let { className } = this.props;
return (
<div className={`popup-container ${ className }`}>
<div className="popup">
<h1>{title}</h1>
</div>
{showStartButton ? <button className="fancy-btn" onClick={this.startQuizHandle}>Start Quiz</button> : null}
</div>
);
}
}
export default Popup
class Answers extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnswered: false,
classNames: ['', '', ''],
isDisabled: false
}
this.checkAnswer = this.checkAnswer.bind(this);
}
checkAnswer(e) {
let { isAnswered } = this.props;
this.setState({
isDisabled: true
})
if (!isAnswered) {
let elem = e.currentTarget;
let { correct, increaseScore } = this.props;
let answer = Number(elem.dataset.id);
let updatedClassNames = this.state.classNames;
if (answer === correct) {
updatedClassNames[answer - 1] = 'right';
increaseScore();
}
else {
updatedClassNames[answer - 1] = 'wrong';
}
this.setState({
classNames: updatedClassNames
})
this.props.showButton();
}
}
render() {
let { answers } = this.props;
let { classNames } = this.state;
const { isDisabled } = this.state;
return (
<div id="answers">
<ul>
<li onClick={this.checkAnswer} className={classNames[0]} data-id="1"><p className={isDisabled ? "disabled" : null}><span>A</span> {answers[0]}</p></li>
<li onClick={this.checkAnswer} className={classNames[1]} data-id="2"><p className={isDisabled ? "disabled" : null}><span>B</span> {answers[1]}</p></li>
<li onClick={this.checkAnswer} className={classNames[2]} data-id="3"><p className={isDisabled ? "disabled" : null}><span>C</span> {answers[2]}</p></li>
</ul>
</div>
);
}
}
export default Answers
Convert your last else if to an else so your conditional tests are as follows:
if score >= high score => "...expert!"
else if half score <= score < high score => "... petty good"
else if 0 < score < half score => "..need practice"
else => "did not do so well"
This is the "catch-all" branch of logic for scores that didn't fall into one of the previous test cases.
componentDidUpdate
componentDidUpdate(prevProps) {
let total = this.props.total;
let highScore = total - 2;
let halfScore = total / 2;
const { score } = this.props;
if (score !== prevProps.score) {
if (score >= highScore) {
this.setState({
title: "You are an expert!"
});
} else if (score >= halfScore && score <= highScore) {
this.setState({
title: "You are pretty good at this!"
});
} else if (score < halfScore && score > 0) {
this.setState({
title: "You need some practice."
});
} else {
this.setState({
title: "You did not do too well."
});
}
}
}
Edit
Looks like your initial quiz state is to have total be the length of the data array (presumably the quiz questions?) and score starting at 0. From your logic it is clear score monotonically increases from 0. I think the piece that is missing from the Popup component is checking this "initial state" where the score === 0. My guess is you see the title start at "Quiz" and then after the first correct answer it updates to "You need some practice.". A small refactor of the "checking score" logic within componentDidUpdate will allow you to check it when Popup first mounts when the score is 0.
checkScore = () => {
const { score, total } = this.props;
const highScore = total - 2;
const halfScore = total / 2;
if (score >= highScore) {
this.setState({
title: "You are an expert!"
});
} else if (score >= halfScore && score < highScore) {
this.setState({
title: "You are pretty good at this!"
});
} else if (score < halfScore && score > 0) {
this.setState({
title: "You need some practice."
});
} else {
this.setState({
title: "You did not do too well."
});
}
}
componentDidMount() {
this.checkScore();
}
componentDidUpdate(prevProps) {
const { score } = this.props;
if (prevProps.score !== score) {
this.checkScore();
}
}
Updated the above linked codesandbox.
I'm a newbie to React and I am working on a quiz. What I would like to do now is reset the classnames to it's initial state when you get a new question. I think I want to use componentDidUpdate but not really sure how it works.
componentDidUpdate() {
this.setState({
classNames: ["", "", "", ""]
});
}
Here is the full component code:
class Answers extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnswered: false,
classNames: ["", "", "", ""]
};
this.checkAnswer = this.checkAnswer.bind(this);
}
checkAnswer(e) {
let { isAnswered } = this.props;
if (!isAnswered) {
let elem = e.currentTarget;
let { correct, increaseScore } = this.props;
let answer = Number(elem.dataset.id);
let updatedClassNames = this.state.classNames;
if (answer === correct) {
updatedClassNames[answer - 1] = "right";
increaseScore();
} else {
updatedClassNames[answer - 1] = "wrong";
}
this.setState({
classNames: updatedClassNames
});
this.props.showButton();
}
}
componentDidUpdate() {
this.setState({
classNames: ["", "", "", ""]
});
}
render() {
let { answers } = this.props;
let { classNames } = this.state;
return (
<div id="answers">
<ul>
<li onClick={this.checkAnswer} className={classNames[0]} data-id="1">
<p>{answers[0]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[1]} data-id="2">
<p>{answers[1]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[2]} data-id="3">
<p>{answers[2]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[3]} data-id="4">
<p>{answers[3]}</p>
</li>
</ul>
</div>
);
}
}
export default Answers;
Any help is appreciated! And feedback on the whole code project is also much appreciated since I am learning.
Below is a link the complete project:
https://codesandbox.io/s/another-quiz-mfmop
There is an easy fix for this (and recommended as a React best practice), if you change the key for the answers, working demo: https://codesandbox.io/s/another-quiz-wgycs
<Answers
key={question} // <-- oh hi
answers={answers}
correct={correct}
...
Ideally you would use an id, and since most modern data structures have an id, this would make it ideal to use key={question_id} as the key has to be unique:
{
id: 1
question: 'What does CSS stand for?',
answers: [...],
correct: 3
},
{
id: 2,
....
}
If not, you would have to use prevProps:
componentDidUpdate(prevProps) {
if (this.props.question !== prevProps.question) {
this.setState(....)
}
}
I really recommend the key way, as this will force the creation of a new component, in practice if you need to keep checking for changing props, it can become a bit hard to keep track.
Remember, ideally there should be an id, because if the question text is the same, it would lead to a nasty hard-to-find bug.
Also, instead of saving the classnames, it's better to just save selected as an index and choose the right classname on the render method.
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
I have face this kind of scenario recently. you have to the same code in componentDidUpdate() {}. Here is what I did.
componentDidUpdate(prevProps) {
if (this.props.questions !== prevProps.questions) {
const shuffledAnswerOptions = this.props.questions.map(question =>
question.answer_options &&
this.shuffleArray(question.answer_options)
);
this.setState({
current_question:this.props.questions &&
this.props.questions[0],
question_image_url: this.props.questions &&
this.props.questions[0] &&
this.props.questions[0].question_image_url,
answerOptions: shuffledAnswerOptions[0],
numberOfQuestions: this.props.questions &&
this.props.questions.length
});
}
}
In your case you prevSate parameter as well.
here is a sample implementation:
componentDidUpdate(prevProps, prevState) {
if(this.state.assignment !== prevState.assignment){
document.getElementById(prevState.assignment.id) && document.getElementById(prevState.assignment.id).classList.remove("headactiveperf");
}
if(this.state.assessment !== prevState.assessment){
document.getElementById(prevState.assessment.id) && document.getElementById(prevState.assessment.id).classList.remove("headactiveperf");
}
if(this.state.study_group_id !== prevState.study_group_id){
document.getElementById(prevState.study_group_id) && document.getElementById(prevState.study_group_id).classList.remove("klassactiveperf");
}
}
First of all, You have to add a button to reset the classes names and this button will call a function for resetting them like:
import React from "react";
class Answers extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnswered: false,
classNames: ["", "", "", ""]
};
}
checkAnswer = e => {
let { isAnswered } = this.props;
if (!isAnswered) {
let elem = e.currentTarget;
let { correct, increaseScore } = this.props;
let answer = Number(elem.dataset.id);
let updatedClassNames = this.state.classNames;
if (answer === correct) {
updatedClassNames[answer - 1] = "right";
increaseScore();
} else {
updatedClassNames[answer - 1] = "wrong";
}
this.setState({
classNames: updatedClassNames
});
this.props.showButton();
}
};
reset = () => {
this.setState({
isAnswered: false,
classNames: ["", "", "", ""]
});
};
render() {
let { answers } = this.props;
let { classNames } = this.state;
return (
<div id="answers">
<button onClick={this.reset}>RESET</button>
<ul>
<li onClick={this.checkAnswer} className={classNames[0]} data-id="1">
<p>{answers[0]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[1]} data-id="2">
<p>{answers[1]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[2]} data-id="3">
<p>{answers[2]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[3]} data-id="4">
<p>{answers[3]}</p>
</li>
</ul>
</div>
);
}
}
export default Answers;
The problem is that if you change state in componentDidUpdate it will trigger another update right away and therefore run componentDidUpdate again and result in an infinite loop. So you should either move the setState somewhere else, or put it behind a condition. e.g.:
componentDidUpdate() {
if (!this.state.classNames.every(className => className === "") { // Check if there is an item in the array which doesn't match an empty string ("")
this.setState({ // Only update state if it's necessary
classNames: ["", "", "", ""]
});
}
}
You can find an updated CodeSandbox here
My React state:
//...
this.state = {
mylist: [
{
"id": 0,
"trueorfalse": false
},
{
"id": 1,
"trueorfalse": false
}
]
}
//...
I am trying to update the trueorfalse value based on the id
Here is what I did so far but didn't work:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
var TorF = true
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { TorF }) : el))
})
}
I really want to make it dynamic so the trueorfase will be opposite of what it is now:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { /* if already true set to false or vice versa */ }) : el))
})
}
How can I update my code to have the dynamicity shown in the second example (if possible), otherwise the first example would do just fine
Another solution using map:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mylist: [
{
id: 0,
trueorfalse: false
},
{
id: 1,
trueorfalse: true
}
]
};
}
toggleBoolean = () => {
const ID = Number(this.state.selectedID);
this.setState(prevState => ({
mylist: prevState.mylist.map(item => {
if (item.id === ID) {
return { ...item, trueorfalse: !item.trueorfalse };
} else {
return item;
}
})
}));
};
render() {
return (
<div className="App">
<p>{`State values: ${JSON.stringify(this.state.mylist)}`}</p>
<button onClick={this.toggleBoolean}>Change true/false values</button>
<label>Insert ID:</label>
<input
type="number"
onChange={event => this.setState({ selectedID: event.target.value })}
/>
</div>
);
}
}
I think the following code would accomplish your second question.
var idnum = e.target.id.toString().split("_")[1]
let newList = Array.from(this.state.mylist) //create new array so we don't modify state directly
if (type === 1) {
let objToUpdate = newList.find((el) => el.id === idnum) // grab first element with matching id
objToUpdate.trueorfalse = !objToUpdate.trueorfalse
this.setState( { mylist: newList } )
}
I have an array like below
[
1:false,
9:false,
15:false,
19:false,
20:true,
21:true
]
on click i have to change the value of specific index in an array.
To update value code is below.
OpenDropDown(num){
var tempToggle;
if ( this.state.isOpen[num] === false) {
tempToggle = true;
} else {
tempToggle = false;
}
const isOpenTemp = {...this.state.isOpen};
isOpenTemp[num] = tempToggle;
this.setState({isOpen:isOpenTemp}, function(){
console.log(this.state.isOpen);
});
}
but when i console an array it still shows old value, i have tried many cases but unable to debug.
This is working solution,
import React, { Component } from "react";
class Stack extends Component {
state = {
arr: [
{ id: "1", value: false },
{ id: "2", value: false },
{ id: "9", value: false },
{ id: "20", value: true },
{ id: "21", value: true }
]
};
OpenDropDown = event => {
let num = event.target.value;
const isOpenTemp = [...this.state.arr];
isOpenTemp.map(item => {
if (item.id === num) item.value = !item.value;
});
console.log(isOpenTemp);
this.setState({ arr: isOpenTemp });
};
render() {
let arr = this.state.arr;
return (
<React.Fragment>
<select onChange={this.OpenDropDown}>
{arr.map(item => (
<option value={item.id}>{item.id}</option>
))}
</select>
</React.Fragment>
);
}
}
export default Stack;
i hope it helps!
The problem is your array has several empty value. And functions like map, forEach will not loop through these items, then the index will not right.
You should format the isOpen before setState. Remove the empty value
const formattedIsOpen = this.state.isOpen.filter(e => e)
this.setState({isOpen: formattedIsOpen})
Or use Spread_syntax if you want to render all the empty item
[...this.state.isOpen].map(e => <div>{Your code here}</div>)
I am trying to come up a react exercise for the flip-match cards game: say 12 pairs of cards hide (face down) randomly in a 4x6 matrix, player click one-by-one to reveal the cards, when 2 cards clicked are match then the pair is found, other wise hide both again., gane over when all pairs are found.
let stored = Array(I * J).fill(null).map((e, i) => (i + 1) % (I * J));
/* and: randomize (I * J / 2) pairs position in stored */
class Board extends React.Component {
constructor() {
super();
this.state = {
cards: Array(I*J).fill(null),
nClicked: 0,
preClicked: null,
clicked: null,
};
}
handleClick(i) {
if (!this.state.cards[i]) {
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[i] = stored[i];
return {
cards: upCards,
nClicked: prevState.nClicked + 1,
preClicked: prevState.clicked,
clicked: i,
};
}, this.resetState);
}
}
resetState() {
const preClicked = this.state.preClicked;
const clicked = this.state.clicked;
const isEven = (this.state.nClicked-1) % 2;
const matched = (stored[preClicked] === stored[clicked]);
if (isEven && preClicked && clicked && matched) {
// this.forceUpdate(); /* no effects */
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[preClicked] = null;
upCards[clicked] = null;
return {
cards: upCards,
nClicked: prevState.nClicked,
preClicked: null,
clicked: null,
};
});
}
}
renderCard(i) {
return <Card key={i.toString()} value={this.state.cards[i]} onClick={() => this.handleClick(i)} />;
}
render() {
const status = 'Cards: '+ I + ' x ' + J +', # of clicked: ' + this.state.nClicked;
const cardArray = Array(I).fill(null).map(x => Array(J).fill(null));
return (
<div>
<div className="status">{status}</div>
{ cardArray.map((element_i, index_i) => (
<div key={'row'+index_i.toString()} className="board-row">
{ element_i.map((element_j, index_j) => this.renderCard(index_i*J+index_j))
}
</div>
))
}
</div>
);
}
}
Essentially, Board constructor initialize the state, and handleClick() calls setState() to update the state so it trigger the render of the clicked card's value; the callback function resetState() is that if the revealed two card did not match, then another setState() to hide both.
The problem is, the 2nd clicked card value did not show before it goes to hide. Is this due to React combine the 2 setState renderings in one, or is it rendering so fast that we can not see the first rendering effects before the card goes hide? How to solve this problem?
You're passing resetState as the callback to setState, so I would expect after the initial click your state will be reset.
You might want to simplify a bit and do something like this:
const CARDS = [
{ index: 0, name: 'Card One', matchId: 'match1' },
{ index: 1, name: 'Card Two', matchId: 'match2' },
{ index: 2, name: 'Card Three', matchId: 'match1', },
{ index: 3, name: 'Card Four', 'matchId': 'match2' },
];
class BoardSim extends React.Component {
constructor(props) {
super(props);
this.state = {
cardsInPlay: CARDS,
selectedCards: [],
checkMatch: false,
updateCards: false
};
...
}
...
componentDidUpdate(prevProps, prevState) {
if (!prevState.checkMatch && this.state.checkMatch) {
this.checkMatch();
}
if (!prevState.updateCards && this.state.updateCards) {
setTimeout(() => {
this.mounted && this.updateCards();
}, 1000);
}
}
handleCardClick(card) {
if (this.state.checkMatch) {
return;
}
if (this.state.selectedCards.length === 1) {
this.setState({ checkMatch: true });
}
this.setState({
selectedCards: this.state.selectedCards.concat([card])
});
}
checkMatch() {
if (this.selectedCardsMatch()) {
...
}
else {
...
}
setTimeout(() => {
this.mounted && this.setState({ updateCards: true });
}, 2000);
}
selectedCardsMatch() {
return this.state.selectedCards[0].matchId ===
this.state.selectedCards[1].matchId;
}
updateCards() {
let cardsInPlay = this.state.cardsInPlay;
let [ card1, card2 ] = this.state.selectedCards;
if (this.selectedCardsMatch()) {
cardsInPlay = cardsInPlay.filter((card) => {
return card.id !== card1.id && card.id !== card2.id;
});
}
this.setState({
selectedCards: [],
cardsInPlay,
updateCards: false,
checkMatch: false
});
}
render() {
return (
<div>
{this.renderCards()}
</div>
);
}
renderCards() {
return this.state.cardsInPlay.map((card) => {
return (
<div key={card.name} onClick={() => this.handleCardClick(card)}>
{card.name}
</div>
);
});
}
...
}
I've created a fiddle for this you can check out here: https://jsfiddle.net/andrewgrewell/69z2wepo/82425/