[英]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;
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
更改,因此需要在依赖项列表中包含ref
或ref.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.