简体   繁体   English

React Hooks - Ref 在 useEffect 中不可用

[英]React Hooks - Ref is not available inside useEffect

I am using ReactHooks.我正在使用 ReactHooks。 I am trying to access ref of User component in useEffect function, but I am getting elRef.current value as null , though I passed elRef.current as second argument to useEffect .我试图在useEffect function 中访问User组件的ref ,但我得到elRef.current值为null ,尽管我将elRef.current作为第二个参数传递给useEffect I am supposed to get reference to an element, but outside (function body) of useEffect , ref value is available.我应该引用一个元素,但是在useEffect之外(函数体), ref值是可用的。 Why is that?这是为什么? How can I get elRef.current value inside useEffect ?如何在useEffect中获取elRef.current值?

code代码

import React, { Component, useState, useRef, useEffect } from "react";

const useFetch = url => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(
    () => {
      setIsLoading(true);
      fetch(url)
        .then(response => {
          if (!response.ok) throw Error(response.statusText);
          return response.json();
        })
        .then(json => {
          setIsLoading(false);
          setData(json.data);
        })
        .catch(error => {
          setIsLoading(false);
          setError(error);
        });
    },
    [url]
  );

  return { data, isLoading, error };
};

const User = ({ id }) => {
  const elRef = useRef(null);
  const { data: user } = useFetch(`https://reqres.in/api/users/${id}`);

  useEffect(() => {
    console.log("ref", elRef.current);
  }, [elRef.current]);
  if (!user) return null;
  return <div ref={elRef}>{user.first_name + " " + user.last_name}</div>;
};

class App extends Component {
  state = {
    userId: 1
  };

  handleNextClick = () => {
    this.setState(prevState => ({
      userId: prevState.userId + 1
    }));
  };

  handlePrevNext = () => {
    this.setState(prevState => ({
      userId: prevState.userId - 1
    }));
  };
  render() {
    return (
      <div>
        <button
          onClick={() => this.handlePrevClick()}
          disabled={this.state.userId === 1}
        >
          Prevoius
        </button>
        <button onClick={() => this.handleNextClick()}>Next</button>
        <User id={this.state.userId} />
      </div>
    );
  }
}

export default App;

Codesandbox link代码沙盒链接

Thanks !谢谢 !

You should use useCallback instead of useRef as suggested in the reactjs docs .您应该按照 reactjs 文档中的建议使用useCallback而不是 useRef 。

React will call that callback whenever the ref gets attached to a different node.每当 ref 附加到不同的节点时,React 都会调用该回调。

Replace this:替换这个:

const elRef = useRef(null);
useEffect(() => {
    console.log("ref", elRef.current);
}, [elRef.current]);

with this:有了这个:

const elRef = useCallback(node => {
    if (node !== null) {
        console.log("ref", node); // node = elRef.current
    }
}, []);

It's a predictable behaviour.这是一种可预测的行为。

As mentioned @estus you faced with this because first time when it's called on componentDidMount you're getting null (initial value) and get's updated only once on next elRef changing because, actually, reference still being the same.正如@estus所提到的,您遇到了这个问题,因为第一次在componentDidMount上调用它时,您会得到null (初始值),并且 get 仅在下一次elRef更改时更新一次,因为实际上,引用仍然相同。

If you need to reflect on every user change, you should pass [user] as second argument to function to make sure useEffect fired when user is changed.如果您需要反思每个用户更改,您应该将[user]作为第二个参数传递给函数,以确保在用户更改时触发useEffect

Here is updated sandbox.是更新的沙箱。

Hope it helped.希望它有所帮助。

useEffect is used as both componentDidMount and componentDidUpdate, at the time of component mount you added a condition: useEffect 用作​​ componentDidMount 和 componentDidUpdate,在组件挂载时添加了一个条件:

if (!user) return null;
return <div ref={elRef}>{user.first_name + " " + user.last_name}</div>;

because of the above condition at the time of mount, you don't have the user, so it returns null and div is not mounted in the DOM in which you are adding ref, so inside useEffect you are not getting elRef's current value as it is not rendered.由于挂载时的上述条件,您没有用户,因此它返回 null 并且 div 未挂载在您添加 ref 的 DOM 中,因此在 useEffect 中,您没有获得 elRef 的当前值未呈现。

And on the click of next as the div is mounted in the dom you got the value of elRef.current.当 div 安装在 dom 中时,在单击 next 时,您将获得 elRef.current 的值。

The assumption here is that useEffect needs to detect changes to ref.current , so needs to have the ref or ref.current in the dependencies list.这里的假设是useEffect需要检测对ref.current更改,因此需要在依赖项列表中包含refref.current I think this is due to es-lint being a bit over-pedantic.我认为这是由于es-lint有点过于迂腐。

Actually, the whole point of useEffect is that it guarantees not to run until the rendering is complete and the DOM is ready to go.实际上, useEffect的全部意义在于它保证在渲染完成并且 DOM 准备就绪之前不会运行。 That is how it handles side-effects.这就是它处理副作用的方式。

So by the time useEffect is executed, we can be sure that elRef.current is set.所以到useEffect执行时,我们可以确定elRef.current已经设置。

The problem with your code is that you don't run the renderer with <div ref={elRef}...> until after user is populated.您的代码的问题在于,在填充user之前,您不会使用<div ref={elRef}...>运行渲染器。 So the DOM node you want elRef to reference doesn't yet exist.所以你想让elRef引用的 DOM 节点还不存在。 That is why you get the null logging - nothing to do with dependencies.这就是为什么你得到null日志的原因——与依赖无关。


BTW: one possible alternative is to populate the div inside the effect hook:顺便说一句:一种可能的替代方法是在效果钩子内填充 div:

useEffect(
  () => {
    if(!user) return;
    elRef.current.innerHTML = `${user.first_name} ${user.last_name}`;
  }, [user]
);

That way the if (!user) return null;这样if (!user) return null; line in the User component is unnecessary. User 组件中的行是不必要的。 Remove it, and elRef.current is guaranteed to be populated with the div node from the very beginning.删除它, elRef.current保证从一开始就用 div 节点填充。

When you use a function as a ref , it is called with the instance when it is ready.当您将函数用作 ref 时,它会在实例准备好时与实例一起调用。 So the easiest way to make the ref observable is to use useState instead of useRef :因此,使 ref 可观察的最简单方法是使用useState而不是useRef

const [element, setElement] = useState<Element | null>(null);
return <div ref={setElement}></div>;

Then you can use it in dependency arrays for other hooks, just like any other const value:然后你可以在其他钩子的依赖数组中使用它,就像任何其他const值一样:

useEffect(() => {
  if (element) console.log(element);
}, [element]);

set a useEffect on the elem's.current:在 elem's.current 上设置 useEffect:

 let elem = useRef(); useEffect(() => { //... }, [elem.current]);

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

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