【问题标题】:React - UseEffect not re-rendering with new data?React - UseEffect 不使用新数据重新渲染?
【发布时间】:2021-03-24 00:10:34
【问题描述】:

这是我的 React Hook:

function Student(props){

    const [open, setOpen] = useState(false);
    const [tags, setTags] = useState([]);

    useEffect(()=>{
        let input = document.getElementById(tagBar);
        input.addEventListener("keyup", function(event) {
        if (event.keyCode === 13) {
            event.preventDefault();
            document.getElementById(tagButton).click();
        }
        });
    },[tags])

    const handleClick = () => {
        setOpen(!open);
    };

    function addTag(){
        let input = document.getElementById(tagBar);
        let tagList = tags;
        tagList.push(input.value);
        console.log("tag");
        console.log(tags);
        console.log("taglist");
        console.log(tagList);
        setTags(tagList);
    }

    const tagDisplay = tags.map(t => {
        return <p>{t}</p>;
    })

return(
<div className="tags">
  <div>
   {tagDisplay}
  </div>
  <input type='text' id={tagBar} className="tagBar" placeholder="Add a Tag"/>
  <button type="submit" id={tagButton} className="hiddenButton" onClick={addTag}></button>
<div>
);

我想要做的是能够为这些学生元素添加一个标签(我有多个,但每个都是相互独立的),并且添加的标签显示在我的显示的标签部分。我还需要通过在输入字段上按 Enter 来触发此操作。

由于我不确定的原因,我必须将 enter 绑定放在 useEffect 中(可能是因为输入元素尚未渲染)。

现在,当我在输入字段中输入文本时,它会正确更新 tags/tagList 变量,但可以通过 console.logs 看到,即使我将标签设置为 useEffect 中的重新渲染条件(以及事实上它也是我的状态之一),我的页面没有使用添加的标签进行更新

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    你是对的,第一次渲染时该元素不存在,这就是为什么useEffect 可以派上用场。至于为什么它不重新渲染,您将标签作为依赖项传递以检查重新渲染。问题是,标签是一个数组,这意味着它比较的是内存引用而不是内容。

    var myRay = [];
    var anotherRay = myRay;
    
    var isSame = myRay === anotherRay; // TRUE
    myRay.push('new value');
    
    var isStillSame = myRay === anotherRay; // TRUE
    // setTags(sameTagListWithNewElementPushed)
    // React says, no change detected, same memory reference, skip
    

    由于您的添加标记方法将新元素推送到同一个数组引用中,useEffect 认为它是同一个数组并且不会重新触发。最重要的是,React 只会在其 props 更改、状态更改或请求强制重新渲染时重新渲染。在你的情况下,你没有改变状态。试试这个:

        function addTag(){
            let input = document.getElementById(tagBar);
            let tagList = tags;
    
            // Create a new array reference with the same contents
            // plus the new input value added at the end
            setTags([...tagList, input.value]);
        }
    

    如果您不想使用useEffect,我相信您也可以使用useRef 在创建节点时访问它。或者您可以使用onKeyDownonKeyPress 将回调直接放在节点本身上

    【讨论】:

      【解决方案2】:

      我可以在您的代码中找到一些错误。首先,您自己附加事件侦听器,这在反应中不是首选。另一方面,如果您确实需要在 useEffect 中向 DOM 添加侦听器,您也应该在您之后清理,否则,将在组件重新渲染时添加另一个侦听器。

      useEffect( () => {
        const handleOnKeyDown = ( e ) => { /* code */ }
      
        const element = document.getElementById("example")
        element.addEventListener( "keydown", handleOnKeyDown )
      
      
        return () => element.removeEventListener( "keydown", handleOnKeyDown ) // cleaning after effect
      }, [tags])
      

      使用 React 处理事件的更好方法是使用合成事件和组件道具。

      const handleOnKeyDown = event => {
        /* code */
      }
      
      return (
        <input onKeyDown={ handleOnKeyDown } />
      )
      

      第二件事是每个 React 组件都应该有唯一的键。没有它,React 可能无法正确渲染子列表并渲染所有子列表,这可能会对大型列表或具有许多子项的列表项产生不良的性能影响。默认情况下,使用 map 时未设置此键,因此您应自行处理。

      tags.map( (tag, index) => {
        return <p key={index}>{tag}</p>;
      })
      

      第三,当您尝试添加标签时,您再次查询 DOM 而不使用反应语法。此外,您根据以前的版本更新当前状态可能会导致问题,因为 setState 是异步函数,有时无法立即更新状态。

      const addTag = newTag => {
        setState( prevState => [ ...prevState, ...newTage ] ) // when you want to update state with previous version you should pass callback which always get correct version of state as parameter
      }
      

      我希望这篇评论可以帮助你理解 React。

      function Student(props) {
        const [tags, setTags] = useState([]);
        const [inputValue, setInputValue] = useState("");
      
        const handleOnKeyDown = (e) => {
          if (e.keyCode === 13) {
            e.preventDefault();
            addTag();
          }
        };
      
        function addTag() {
          setTags((prev) => [...prev, inputValue]);
          setInputValue("");
        }
      
        return (
          <div className="tags">
            <div>
              {tags.map((tag, index) => (
                <p key={index}>{tag}</p>
              ))}
            </div>
            <input
              type="text"
              onKeyDown={handleOnKeyDown}
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
              placeholder="Add a Tag"
            />
            <button type="submit" onClick={addTag}>
              ADD
            </button>
          </div>
        );
      }
      

      【讨论】:

      • 跟进您的一些观点,在实施 Spidy 的解决方案(在技术上有效)之后,我现在遇到了新问题。例如,在第一次加载时,一切正常,但是当我开始过滤查询时,由于不匹配而卸载的所有实体,当它们重新加载时(通过我清除 searchQuery),它们将失去添加它们的能力自己的标签
      • 这些新错误的原因是否与您提出的一些观点有关?
      猜你喜欢
      • 2020-07-06
      • 1970-01-01
      • 2022-06-18
      • 2020-01-06
      • 2022-01-27
      • 1970-01-01
      • 1970-01-01
      • 2022-11-17
      • 2021-04-27
      相关资源
      最近更新 更多