簡體   English   中英

react 無法使用 forwardRef 和 useRef 讀取 ref 的 null 的屬性

[英]react cannot read properties of null of ref with forwardRef and useRef

我正在使用 function 組件。 我使用useRef來存儲previousValueCount因為我不希望它在更新時重新呈現組件。 只要我的useRef聲明位於我的代碼使用該 ref in 讀取和寫入的組件中,這就完美了 然而,意識到我可能需要這個更高的樹,我轉向道具。 但是,當我將useRef聲明移動到父級並將 ref 作為道具傳遞時,代碼似乎中斷了,告訴我它是 null,不管初始化值如何,它之前在子級中工作。 據我了解,我無法將 ref 作為道具傳遞,所以我轉向React.forwardRef 我嘗試了同樣的事情但沒有解決方案,並且在下面分享了一個類似的錯誤。 如何將 ref 作為 prop 或向下組件樹傳遞?

應用程序組件:

function App() {
  const [itemsLeftCount, setItemsleftCount] = useState(0);

  return (
    <ToDos
      itemsLeftCount={itemsLeftCount}
      setItemsLeftCount={setItemsLeftCount}
    ></ToDos>
  )
}

父組件:

function ToDos(props) {
  const prevItemsLeftCountRef = useRef(0);

  return (
    <ToDoBox
      itemsLeftCount={props.itemsLeftCount}
      setItemsLeftCount={props.setItemsLeftCount}
      ref={prevItemsLeftCountRef}
    ></ToDoBox>

  {

兒童組件:

const ToDoBox = React.forwardRef((props, ref) => {
  useEffect(() => {
    //LINE 25 Error points to right below comment
    ref.prevItemsLeftCountRef.current = props.itemsLeftCount;
  }, [props.itemsLeftCount]);

  useEffect(() => {
    //ON MOUNT
    props.setItemsLeftCount(ref.prevItemsLeftCountRef.current + 1);

    //ON UNMOUNT
    return function removeToDoFromItemsLeft() {
      //this needs to only run after setitemsleftcount state is for sure done updating
      props.setItemsLeftCount(ref.prevItemsLeftCountRef.current - 1);
    };
  }, []);
})

我收到此錯誤:未捕獲的類型錯誤:無法在 ToDoBox.js:25:1 讀取 null 的屬性(讀取“prevItemsLeftCountRef”)

@Mustafa Walid

function ToDos(props) {
  const prevItemsLeftCountRef = useRef(0);
  const setPrevItemsLeftCountRef = (val) => {
    prevItemsLeftCountRef.current = val;
  };

 return (
   <ToDoBox
     itemsLeftCount={props.itemsLeftCount}
     setItemsLeftCount={props.setItemsLeftCount}
     prevItemsLeftCountRef={prevItemsLeftCountRef}
     setPrevItemsLeftCountRef={setPrevItemsLeftCountRef}
    ></ToDoBox>
  )
}

function ToDoBox(props) {
  useEffect(() => {
    //itemsLeftCount exists further up tree initialized at 0
    props.setPrevItemsLeftCountRef(props.itemsLeftCount); 
  }, [props.itemsLeftCount]);

}

  • 可以使用 function 修改父ref ,然后將此 function 傳遞給它的孩子。 然后,孩子將使用此 function 修改父母的ref
const MainComponent = () => {
  const myRef = useRef("old Ref");

  const changeRef = (val) => {
    myRef.current = val;
  };

  return <SubComponent changeRef={changeRef} />;
};

const SubComponent = ({ changeRef }) => {
  changeRef("new Ref");

  return <h1>Changed Parent's Ref!</h1>;
};

問題

您不需要任何參考。 你應該使用 React state。 我真的無法准確確定導致雙倍初始遞減的任何特定問題,但是代碼中有很多問題都值得一提。

解決方案

首先,在ToDoBox組件中掛載useEffect鈎子。 我看到您試圖保留和維護兩個項目計數 state 值,當前值和先前值。 這是完全多余的,如代碼所示,可能會導致 state 同步問題。 簡單地說,您應該在此處使用功能性 state 更新來正確訪問上一個項目計數 state 值來增加或減少它。 您可能還需要檢查當前ToDoBox組件是否不是返回的清理 function 中的“創建框”,以確保創建 todos 的ToDoBox不會意外更新項目計數。

例子:

useEffect(() => {
  // RUNS ON MOUNT
  if (!isCreateBox) {
    setItemsLeftCount(count => count + 1);
  }
  
  return function removeToDoFromItemsLeft() {
    if (!isCreateBox) {
      setItemsLeftCount(count => count - 1);
    }
  };
}, []);

僅此一項就足以阻止項目計數雙減問題。

我注意到的其他問題

State 突變

添加刪除待辦事項的功能都在改變 state。 這就是為什么您必須在 ToDos 中添加輔助state ToDos以強制您的應用程序重新渲染並顯示突變。

addToDoToList

這里updatedArr是對dataArr state 對象/數組的引用,而 function直接推入數組(突變),然后將相同的對象/數組引用保存回 Z9ED39E2EA9364586B3EZEFA 中。

function addToDoToList() {
  let updatedArr = dataArr;   // <-- saved reference to state
  updatedArr.push(dataInput); // <-- mutation!!
  setDataArr(updatedArr);     // <-- same reference back into state
}
function handleClickCreateButton() {
  addToDoToList(); // <-- mutates state
  //force a rerender of todos
  setState({});    // <-- force rerender to see mutation
}

handleClickDeleteButton

同樣,這里的updateArr是對當前 state 的引用。 Array.prototype.splice就地改變數組。 再一次,變異的 state 引用被保存回 state 並且需要強制重新渲染。

function handleClickDeleteButton() {
  // if splice from dataArr that renders a component todobox for each piece of data
  // if remove data from dataArr, component removed...
  let updatedArr = dataArr;    // <-- saved reference to state
  updatedArr.splice(index, 1); // <-- mutation!!
  setDataArr(updatedArr);      // <-- same reference back into state

  // then need to rerender todos... I have "decoy state" to help with that
  setState({});
}

奇怪的是,圍繞此代碼的注釋意味着您甚至知道/理解發生了什么事。

這里的解決方案是再次使用功能 state 更新來正確更新之前的 state並同時返回新的對象/數組引用,因此 React 會看到 state 更新並觸發了 rerender 我建議還向待辦事項添加一個id GUID,以便更輕松地識別它們。 這比使用數組索引要好。

例子:

import { nanoid } from 'nanoid';

...

function addTodo() {
  // concat to and return a new array reference
  setDataArr(data => data.concat({
    id: nanoid(),
    todo: dataInput,
  }));
}

function removeTodo(id) {
  // filter returns a new array reference
  setDataArr(data => data.filter(todo => todo.id !== id));
}

其他一般設計問題

  • 道具鑽取根itemsLeftCount state 和setItemsLeftCount state 更新程序 function 一直到葉子ToDoBox組件。
  • 不集中控制對dataArr state 不變量的控制。
  • 使用映射的數組索引作為 React 鍵。
  • 嘗試計算嵌套子項中的“派生”state,即項目數。

這是您的代碼的簡化/縮小的工作版本:

應用程序

function App() {
  const [itemsLeftCount, setItemsLeftCount] = useState(0);

  return (
    <div>
      <ToDos setItemsLeftCount={setItemsLeftCount} />
      <div>{itemsLeftCount} items left</div>
    </div>
  );
}

待辦事項

import { nanoid } from "nanoid";

function ToDos({ setItemsLeftCount }) {
  const [dataInput, setInputData] = useState("");
  const [dataArr, setDataArr] = useState([]);

  useEffect(() => {
    // Todos updated, update item count in parent
    setItemsLeftCount(dataArr.length);
  }, [dataArr, setItemsLeftCount]);

  function getInputData(event) {
    setInputData(event.target.value);
  }

  function addTodo() {
    setDataArr((data) =>
      data.concat({
        id: nanoid(),
        todo: dataInput
      })
    );
  }

  function removeTodo(id) {
    setDataArr((data) => data.filter((todo) => todo.id !== id));
  }

  return (
    <div>
      <ToDoBox isCreateBox getInputData={getInputData} addTodo={addTodo} />
      {dataArr.map((data) => (
        <ToDoBox key={data.id} data={data} removeTodo={removeTodo} />
      ))}
    </div>
  );
}

待辦事項框

function ToDoBox({ addTodo, data, getInputData, isCreateBox, removeTodo }) {
  if (isCreateBox) {
    return (
      <div>
        <input
          placeholder="Create a new todo..."
          onChange={getInputData}
          tabIndex={-1}
        />
        <span onClick={addTodo}>+</span>
      </div>
    );
  } else {
    return (
      <div>
        <span>{data.todo}</span>{" "}
        <span onClick={() => removeTodo(data.id)}>X</span>
      </div>
    );
  }
}

編輯 react-cannot-read-properties-of-null-of-ref-with-forwardref-and-useref

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM