I’m trying to refactor a Class component to Functional, but i’m pretty new to React and got myself confused with some things and got the
Invalid value for prop
disabled
on tag.
the whole code is here, may I know if I refactored it correctly or how can I fix this?
Original Class code :
import { Component } from "react";
import "./Game.css";
import Dice from "./Dice";
import Rule from "./Rule";
class Game extends Component {
state = {
dices: Array(this.props.nOfDices).fill(1),
locked: Array(this.props.nOfDices).fill(false),
rotation: Array(this.props.nOfDices).fill(0),
rollsRemaining: 3,
isRolling: false,
rules: this.props.rules.map( r => ({...r})),
score: 0,
bestScore: window.localStorage.getItem("bestScore") || "0"
};
componentDidMount() {
this.roll();
};
roll = () => {
const { dices, locked, rotation, rollsRemaining } = this.state;
const dcs = dices.map( (d, i) => locked[i] ? d : this.getRandDiceNum());
const rot = rotation.map( (rotation, i) => locked[i] ? rotation : this.getRandRotation());
this.setState({ dices: dcs, rotation: rot, isRolling: true, rollsRemaining: rollsRemaining - 1 });
setTimeout( () => this.setState({ isRolling: false }), 500 );
};
getRandRotation() {
return Math.random() * (Math.random() < .5 ? - 10 : 10);
};
getRandDiceNum() {
return Math.floor(Math.random() * 6) +1;
};
lock = indx => {
const locked = [ ...this.state.locked ];
locked[indx] = !locked[indx];
this.setState({ locked });
};
getBtnTxt = () => {
const { isRolling, rollsRemaining, rules } = this.state;
if (this.isGameOver(rules)) return "Game over";
if (isRolling && rollsRemaining >= 2) return "Starting round...";
if (isRolling) return "Rolling...";
const msg = ["0 Rolls Remaining", "1 Roll Remaining", "2 Rolls Remaining"];
return msg[rollsRemaining];
};
updateScore = rule => {
const rules = this.state.rules.map( r => ({...r}));
const curr = rules.find( r => r.name === rule.name);
curr.score = curr.calc(this.state.dices);
const locked = this.state.locked.map( el => el = false);
const score = this.sumScores(rules);
this.setState({ rules, locked, rollsRemaining: 3, score }, this.roll);
};
renderRules = (start = 0, end = this.state.rules.length) => {
const ruleList = [];
for(let i = start; i < end; i++) {
const disabled = this.state.rules[i].score !== null;
ruleList.push(
<Rule
rule={this.state.rules[i]}
key={i}
isDisabled={disabled}
updateScore={this.updateScore}
/>
);
};
return ruleList;
};
sumScores(rules) {
return rules.reduce( (acc, curr) => acc += (curr.score ? curr.score : 0), 0);
};
reset = () => {
const { score, bestScore } = this.state;
const newBestScore = this.getBestScore(score, bestScore);
this.setState({
dices: Array(this.props.nOfDices).fill(1),
locked: Array(this.props.nOfDices).fill(false),
rotation: Array(this.props.nOfDices).fill(0),
rollsRemaining: 3,
isRolling: false,
rules: this.props.rules.map( r => ({...r})),
score: 0,
bestScore: newBestScore
}, this.roll);
};
getBestScore(score, bestScore) {
if (score > bestScore) {
window.localStorage.setItem("bestScore", score);
return score;
}
return bestScore;
};
isGameOver(rules) {
for (let r of rules) {
if (r.score === null) return false;
}
return true;
};
render() {
const { dices, isRolling, rollsRemaining, locked, rotation, rules, score, bestScore } = this.state;
const isGameOver = this.isGameOver(rules);
return (
<div className="Game">
<p className="Game__best-score">Best score: {bestScore}</p>
<button
className={`Game__reset-btn ${isGameOver && "Game__reset-btn--active"}`}
onClick={this.reset}
>
Reset
</button>
<header className="Game__header">
<h1 className="Game__title"> Yahtzee! </h1>
<div className="Game__dices">
{dices.map( (d,i) => (
<Dice
key={i}
indx={i}
val={d}
isLocked={(locked[i] || isGameOver) ? true : false}
isRolling={isRolling}
rotation={`${rotation[i]}deg`}
lock={!isGameOver ? this.lock : null}
/>
))}
</div>
<button
className="Game__btn"
disabled={isGameOver || isRolling || rollsRemaining < 1}
onClick={this.roll}
>
{this.getBtnTxt()}
</button>
</header>
<main className="Game__scores">
<article className="Game__scores-box">
<h2 className="Game__scores-header">Upper</h2>
<ul className="Game__scores-list">
{this.renderRules(0, 6)}
</ul>
</article>
<article className="Game__scores-box">
<h2 className="Game__scores-header">Lower</h2>
<ul className="Game__scores-list">
{this.renderRules(6)}
</ul>
</article>
<article className="Game__scores-box">
<h2 className="Game__scores-header">Total: {score}</h2>
</article>
</main>
</div>
)
}
};
export default Game;
My refactored Functional code :
import "./Game.css";
import Dice from "./Dice";
import Rule from "./Rule";
import React, { useEffect, useState } from "react";
const Game = ({ rules: _rules, nOfDices }) => {
const [isRolling, setisRolling] = useState(false);
const [score, setScore] = useState(0);
const [rollsRemaining, setRollsRemaining] = useState(3);
const [bestScore, setBestScore] = useState(window.localStorage.getItem('bestScore') || '0');
// nOfDices
const [dices, setDices] = useState([Array(nOfDices).fill(1)]);
const [locked, setLocked] = useState([Array(nOfDices).fill(false)]);
const [rotation, setRotation] = useState([Array(nOfDices).fill(0)]);
// rules
const [rules, setRules] = useState(_rules.map((r) => ({ ...r })));
// update state if `nOfDices` changes in props
useEffect(() => {
setDices([Array(nOfDices).fill(1)]);
setLocked([Array(nOfDices).fill(false)]);
setRotation([Array(nOfDices).fill(0)]);
}, [nOfDices]);
// update state if `_rules` changes in props
useEffect(() => {
setRules(_rules.map((r) => ({ ...r })));
}, [_rules]);
useEffect(() => {
roll();
//eslint-disable-next-line
}, []);
const roll = () => {
const dcs = dices.map((d, i) => locked[i] ? d : getRandDiceNum());
const rot = rotation.map((rotation, i) => locked[i] ? rotation : getRandRotation());
setDices(dcs)
setRotation(rot)
setisRolling(true)
setRollsRemaining(rollsRemaining - 1)
setTimeout(() => setisRolling(false), 500);
};
const getRandRotation = () => {
return Math.random() * (Math.random() < .5 ? - 10 : 10);
};
const getRandDiceNum = () => {
return Math.floor(Math.random() * 6) + 1;
};
const lock = indx => {
locked[indx] = !locked[indx];
setLocked(true)
};
const getBtnTxt = () => {
if (isGameOver(rules)) return "Game over";
if (isRolling && rollsRemaining >= 2) return "Starting round...";
if (isRolling) return "Rolling...";
const msg = ["0 Rolls Remaining", "1 Roll Remaining", "2 Rolls Remaining"];
return msg[rollsRemaining];
};
const updateScore = rule => {
const curr = rules.find(r => r.name === rule.name);
curr.score = curr.calc(dices);
setRules(rules.map(r => ({ ...r })))
setLocked(locked.map(el => el = false))
setRollsRemaining(3)
setScore(sumScores(rules))
roll();
};
const renderRules = (start = 0, end = rules.length) => {
const ruleList = [];
for (let i = start; i < end; i++) {
const disabled = rules[i].score !== null;
ruleList.push(
<Rule
rule={rules[i]}
key={i}
isDisabled={disabled}
updateScore={updateScore}
/>
);
};
return ruleList;
};
const sumScores = (rules) => {
return rules.reduce((acc, curr) => acc += (curr.score ? curr.score : 0), 0);
};
const reset = (nOfDices) => {
// const { score, bestScore } = state;
const newBestScore = getBestScore(score, bestScore);
setDices(dices)
setLocked(locked)
setRotation(rotation)
setRollsRemaining(rollsRemaining)
setisRolling(false)
setRules(rules)
setScore(0)
setBestScore(newBestScore)
roll()
};
const getBestScore = (score, bestScore) => {
if (score > bestScore) {
window.localStorage.setItem("bestScore", score);
return score;
}
return bestScore;
};
const isGameOver = (rules) => {
for (let r of rules) {
if (r.score === null) return false;
}
return true;
};
//const { dices, isRolling, rollsRemaining, locked, rotation, rules, score, bestScore } = state;
//const isGameOver = isGameOver(rules)
return (
<div className="Game">
<p className="Game__best-score">Best score: {bestScore}</p>
<button
className={`Game__reset-btn ${isGameOver && "Game__reset-btn--active"}`}
onClick={reset}
>
Reset
</button>
<header className="Game__header">
<h1 className="Game__title"> Yahtzee! </h1>
<div className="Game__dices">
{dices.map((d, i) => (
<Dice
key={i}
indx={i}
val={d}
isLocked={(locked[i] || isGameOver) ? true : false}
isRolling={isRolling}
rotation={`${rotation[i]}deg`}
lock={!isGameOver ? lock : null}
/>
))}
</div>
<button
className="Game__btn"
disabled={isGameOver || isRolling || rollsRemaining < 1}
onClick={roll}
>
{getBtnTxt()}
</button>
</header>
<main className="Game__scores">
<article className="Game__scores-box">
<h2 className="Game__scores-header">Upper</h2>
<ul className="Game__scores-list">
{renderRules(0, 6)}
</ul>
</article>
<article className="Game__scores-box">
<h2 className="Game__scores-header">Lower</h2>
<ul className="Game__scores-list">
{renderRules(6)}
</ul>
</article>
<article className="Game__scores-box">
<h2 className="Game__scores-header">Total: {score}</h2>
</article>
</main>
</div>
)
};
export default Game;
How can I fix it so it actually works?