简体   繁体   English

重新渲染元素后反应参考 null

[英]React ref null after re-rendering element

I have a to-do list where tasks can be either a single big textarea (called dataArea here) or a list of those textareas.我有一个待办事项列表,其中任务可以是单个大文本区域(此处称为 dataArea)或这些文本区域的列表。 Those textareas should grow in height as content is added, which I do by setting the height to its scrollHeight on input (via handleInput).随着内容的添加,这些文本区域的高度应该会增加,我通过在输入时将高度设置为它的滚动高度来做到这一点(通过句柄输入)。 What I want to do is let folks toggle between that plain textarea and list of textareas (via toggleChecklist), using state to store the content.我想做的是让人们在纯文本区域和文本区域列表之间切换(通过 toggleChecklist),使用 state 来存储内容。

However, when the content is set via state—not direct user input—the handleInput function isn't reached and I must set that from a different function or manually fire onInput.但是,当内容是通过状态而不是直接用户输入设置时,无法到达句柄输入 function,我必须从不同的 function 设置它或手动触发 onInput。 Either way, I believe I must use a ref (blobRef) to access that element to set its height.无论哪种方式,我相信我必须使用 ref (blobRef) 来访问该元素以设置其高度。 However, blobRef is null after toggling to/from checklist.但是,在与清单之间切换后,blobRef 为 null。 Why is that?这是为什么?

Here's where that's [not] happening in full context (I think only the Form.js file is what needs looking at): https://github.com/werdnanoslen/tasks/blob/help/src/components/Form.js#L85这是 [not] 在完整上下文中发生的地方(我认为只有 Form.js 文件需要查看): https://github.com/werdnanoslen/tasks/blob/help/src/components/Form.js# L85

And here's some code previews:这是一些代码预览:

  const blobRef = useRef(null)

  ...

  function handleInput(e, i) {
    const element = e.target
    if (checklist) {
      let checklistDataCopy = [...checklistData]
      checklistDataCopy[i] = { ...checklistDataCopy[i], data: element.value }
      setChecklistData(checklistDataCopy)
    } else {
      setData(element.value)
    }
    element.style.height = '0'
    element.style.height = element.scrollHeight + 'px'
  }

  function toggleChecklist() {
    setChecklist((prevChecklist) => !prevChecklist)
    if (checklist) {
      const n = String.fromCharCode(13, 10) //newline character
      setData(checklistData.reduce((p, c) => p.concat(c.data + n), ''))
      blobRef.current && blobRef.current.dispatchEvent(new Event('input'))
    }
  }

  function dataArea(item?, index?) {
    return (
      <textarea
        id={item ? item.id : 'add-task'}
        name="data"
        className="input"
        value={item ? item.data : data}
        onKeyDown={(e) => addChecklistItem(e, index)}
        onInput={(e) => handleInput(e, index)}
        onFocus={() => setEditing(true)}
        placeholder={inputLabel}
        rows="1"
        ref={item ? (item.id === newItemId ? lastRef : undefined) : blobRef}
        // HELP ^^^ blobRef is null after toggling to/from checklist
      />
    )
  }

  ...

  return (
    <>
      <form onSubmit={handleSubmit} onBlur={blurCancel}>
        <label htmlFor="add-task" className="visually-hidden">
          {inputLabel}
        </label>
        {checklist ? checklistGroup : dataArea()}
        {isEditing ? editingTemplate : ''}
      </form>
    </>
  )

On first render the ref is null.在第一次渲染时,参考是 null。 Only on the second render will it be populated.只有在第二次渲染时才会填充它。 This is because during the first render pass, the HTML is not yet loaded, so there is no HTML element reference.这是因为在第一次渲染过程中,HTML 尚未加载,因此没有 HTML 元素引用。

This applies to when you switch to the other component and back to dataArea , as when you switch away react sets it to null when it unmounts since the element reference is gone from the DOM.这适用于当您切换到另一个组件并返回到dataArea时,因为当您从 DOM 中移除元素引用时,当它卸载时,react 会将其设置为 null。 So when it remounts again, on first render it will be null.因此,当它再次重新安装时,在第一次渲染时它将是 null。

You can sometimes get around this sort of problem with useLayoutEffect which only runs when the DOM has rendered.有时您可以使用仅在 DOM 渲染时运行的useLayoutEffect解决此类问题。

Your code is a little odd in that you also are conditionally applying a ref.您的代码有点奇怪,因为您也有条件地应用了 ref。 Normally, you want this to be stable, I would expect it to be simply:通常,您希望它稳定,我希望它很简单:

ref={blobRef}

And any branching would be done when you consume the ref, not set it.当您使用 ref 时,任何分支都会完成,而不是设置它。 It will be quite hard to manage otherwise.否则将很难管理。

However , before I go further I should warn you grabbing the element and setting its height is not actually a good idea.然而,在我进一步 go 之前,我应该警告你抓住元素并设置它的高度实际上并不是一个好主意。 This violates react, because you shouldnt change a DOM nodes properties such that they no longer can be reproduced by React.这违反了 react,因为您不应该更改 DOM 节点的属性,这样它们就不能再被 React 复制了。 There is now a different between reality and React's internal VDOM implementation.现在现实和 React 的内部 VDOM 实现之间存在差异。

This is quite tricky though, id recommend just using a library: https://github.com/Andarist/react-textarea-autosize/tree/main/src .不过这很棘手,我建议只使用一个库: https://github.com/Andarist/react-textarea-autosize/tree/main/src These libraries get around the problem by rendering a cloned off-screen text area outside of React's remit and taking the heights from that, then applying them use the style prop on the "real" textarea.这些库通过在 React 的职权范围之外渲染克隆的屏幕外文本区域并从中获取高度,然后使用“真实”文本区域上的style属性应用它们来解决问题。

A few notes around your implementation:关于您的实施的一些注意事项:

  1. On the first render, the ref is null and not undefined because of component mounting or unmounting and setting the state.在第一次渲染时,参考是 null 并且由于组件安装或卸载和设置 state 而未定义。

  2. Your ref is assigned on further renders.您的 ref 被分配给进一步的渲染。

  3. You are doing DOM manipulation by directly assigning heights to DOM element which is not a correct way of doing it.您正在通过直接为 DOM 元素分配高度来进行 DOM 操作,这不是正确的做法。

  4. You can check by console logging that you are not getting the ref in actual as blobRef.current got nothing/undefined and thus handleInput is not triggered, thats why you added an extra && check there.您可以通过控制台日志记录检查您实际上没有获得 ref,因为 blobRef.current 什么都没有/未定义,因此不会触发 handleInput,这就是为什么您在那里添加了一个额外的 && 检查。 You can handle the case of undefined too.你也可以处理 undefined 的情况。 If you don't get any ref then?如果你没有得到任何参考呢?

  5. You cannot achieve this directly, either use a package or take the height from actual DOM instead of VDOM and assign on the actual DOM element.您无法直接实现这一点,要么使用 package ,要么从实际 DOM 而不是 VDOM 获取高度并分配给实际 DOM 元素。

  6. A NPM packages can be helpful in your case as mentioned by @adam-thomas.正如@adam-thomas 所提到的,NPM 包可能对您的情况有所帮助。 Example is: react-textarea-autosize示例是: react-textarea-autosize

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM