意外的useEffect行为

时间:2019-12-11 01:51:52

标签: reactjs

我正在制作子手游戏。单击字母后,它将不再不可单击。

以下是codeandbox的简化版本: https://codesandbox.io/s/intelligent-cori-6b80q

在沙箱中,单击字母时,将更改为“已单击!”。一切似乎都很好。

但是在我的项目中,我得到了奇怪的行为。

AvailableLetter.js

const AvailableLetter = (props) => {

    const [clicked,setClicked]=useState(false);

    const setStuff = () => {
        setClicked(false); // If I delete this I get Error: Maximum update depth exceeded.
        props.setSolved();
    };
    useEffect( setStuff,[clicked] );

    if (clicked)         // IF CLICKED REMAINS TRUE, EVERY LETTER GETS PLAYED.
    {
        if (props.play())
        {
            props.correct();
        }
        else
        {
            props.incorrect();
        }
    }

    const disableLetter = () => {
        setClicked(true);
    };

    let letter=span onClick={disableLetter}>{props.alphabet}</span>;

    if(clicked)    // CODE NEVER GETS HERE!!!!!!!!!!!!!!
    {
        letter = <span>{props.alphabet}</span>;
    }

    return (
        <Ax>                 // Ax is a high order class, just a wrapper
            {letter}
        </Ax>
    );
}

即使被单击,字母仍然保持可单击状态。这不是我想要的。

每个字母均由Letters.js呈现,后者会以a-z格式输入并生成自定义的AvailableLetter。

const availableLetters = [ ...allLetters ].map(
        (alphabet,i) => {
            return (
                <AvailableLetter setSolved={props.setSolved} play={()=>playHandler(alphabet)} correct={()=>props.correct(alphabet)} incorrect={()=>props.incorrect(alphabet)} solution={props.solution} key={i} alphabet={alphabet} />
            );
        }
    );

所以这里要解决的问题是:

- Letters remain clickable even after a click
- If setClicked(false) is removed it causes an infinite loop
const setStuff = () => {
        setClicked(false);    // if removed causes infinite loop
        props.setSolved();
    };

所有这些都很奇怪,因为在codeandbox中,我不需要将setEffect()中的clicked设置为false。

您可以在此处查看所有代码:https://github.com/gunitinug/hangmanerrors/tree/master/src 请查看项目代码,时间不长。

2 个答案:

答案 0 :(得分:0)

您需要了解useEffect的工作方式。每当状态更改时,都会调用useEffect并执行作为回调传递给setStuff的处理程序(在您的情况下为useEffect)。

因此,setStuff不断更新状态,useEffect不断被调用。因此,您看到错误Error: Maximum update depth exceeded

如下所示更改代码:

const AvailableLetter = (props) => {

    const [clicked, setClicked] = useState(false);

    const setStuff = () => {
      if(clicked) {
        if (props.play()) {
          props.correct();
        }
        else {
          props.incorrect();
        }

        setClicked(false);
        props.setSolved();
      }
    };

    useEffect( setStuff, [clicked] );

    return (
        <Ax>
            <button onClick={() => setClicked(true)} disabled={clicked}>{props.alphabet}</button>
        </Ax>
    );
}

如您对问题的评论中所述,这可能不是最好的方法,但是我的代码可以解决您的问题。

答案 1 :(得分:0)

这是一个结构性问题,我不得不委托处理程序函数并将状态声明给父组件Letters.js

我必须使其如丝般顺滑:p

import React, {useState} from 'react';
import AvailableLetter from './AvailableLetter/AvailableLetter';
import DisabledLetter from './DisabledLetter/DisabledLetter';
import classes from './Letters.module.css';

const Letters = (props) => {
    const [lettersMap, setLettersMap]=useState(
        {
            "a":false,"b":false,"c":false,"d":false,"e":false,"f":false,"g":false,"h":false,"i":false,"j":false,"k":false,"l":false,"m":false,"n":false,"o":false,"p":false,"q":false,"r":false,"s":false,"t":false,"u":false,"v":false,"w":false,"x":false,"y":false,"z":false
        }
    );

    const updateClickedHandler = (letter) => {
        setLettersMap(
            {
                ...lettersMap,[letter]:true
            }
        );
    };

    const playHandler = (alphabet) => {
        const solution = props.solution.split('');
        console.log(solution);

        if (solution.indexOf(alphabet)<0)
        {
            console.log('incorrect');
            return false;
        }
        else
        {
            console.log('correct');
            return true;
        }
    }

    const renderedLetters = Object.keys(lettersMap).map(
        (letter,i)=>{
          if (!lettersMap[letter])     //letter is not yet clicked
          {
            return (
                <AvailableLetter updateClicked={updateClickedHandler} setSolved={props.setSolved} play={()=>playHandler(letter)} correct={()=>props.correct(letter)} incorrect={()=>props.incorrect(letter)} solution={props.solution} key={i} alphabet={letter} />
            )
          }
          else                         //letter is clicked
          {
            return (
                <DisabledLetter alphabet={letter} />
            )
          }
        }
    );



    return (    
        <div className={classes.Letters}>
            <p>Solution: {props.solution}</p>
            <div className={classes.AvailableLetters}>
                {renderedLetters}
            </div>
        </div>
    );
}

export default Letters;

import React from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';

const AvailableLetter = (props) => {

    const setStuff = () => {
      if (props.play()) {
        props.correct();
      }
      else {
        props.incorrect();
      }

      props.updateClicked(props.alphabet);
      props.setSolved();
    };

    return (
        <Ax>
          <span className={classes.AvailableLetter} onClick={setStuff}>{props.alphabet}</span>
        </Ax>
    );
}

export default AvailableLetter;

最后,我添加了另一个名为DisabledLetter.js的组件

import React from 'react';
import classes from './DisabledLetter.module.css';

const DisabledLetter = (props) => {
    return (
    <span className={classes.DisabledLetter} >{props.alphabet}</span>
    );
};

export default DisabledLetter;

您可以在此处查看整个源 https://github.com/gunitinug/hangmanclicked/tree/master/src ;)

但是发现了一个问题,当游戏解决后,我需要再点击一个字母以显示“ YOU WIN”消息。

我死后立即显示“游戏结束”。