In React, changes in state force a component to re-render. A ref is for something you want to store that won't force a re-render even if does change. Typically, a DOM node.
Making this work requires a bit of indirection. The ref itself is always the same object. However the ref's properties - i.e. the current attribute - may change.
What is the .current in a React ref?
reactjs - Why is .current null for useRef Hook? - Stack Overflow
reactjs - React: Changing `useRef.current` value of ref not triggering useEffect - Stack Overflow
Should not set the useRef current value during rendering
Videos
In React, changes in state force a component to re-render. A ref is for something you want to store that won't force a re-render even if does change. Typically, a DOM node.
Making this work requires a bit of indirection. The ref itself is always the same object. However the ref's properties - i.e. the current attribute - may change.
It might look like unnecessary in your example, where you only read the ref, but it becomes important and understandable, if you consider that you also want to set a value to the ref.
If the react-people would have implemented useRef without the .current, it actually would work if you never have to set the value (in runtime). But then you wouldn't need a ref at all. E.g. then your example could perfectly fine be written as just:
const myVar = 'Hello world!';
return <h1>{myVar}</h1>
But you always need to set some value to the ref, and that would just be not possible without the .current.
Here I show some examples to illustrate what would have happened, if the react-people would have done it without .current:
example 1: set myVar.current
e.g. consider this working example:
// in the first run of the component:
const myVar = useRef('old value'); // myVar === useRef_returnValue === { current: 'old value' }
myVar.current = 'new value'; // myVar === useRef_returnValue === { current: 'new value' }
// in the next run of the component:
// myVar is a reference to the same,
// now changed useRef_returnValue === { current: 'new value' }
const myVar = useRef('old value');
// (Note that 'old value' in useRef('old value') is only the initial value,
// which doesn't matter anymore after the first run.)
That would not work if it was without the .current:
// in the first run of the component:
let myVar = useRef('old value'); // myVar === useRef_returnValue === 'old value'
myVar = 'new value'; // myVar is a completely new string 'new value', no reference to the useRef_returnValue anymore.
// in the next run of the component:
// myVar is a reference to the same,
// unchanged useRef_returnValue, still with value 'old value'.
let myVar = useRef('old value');
example 2: ref-property
Even in cases where you might think you never want to set a value, e.g.:
const inputRef = useRef();
return <input ref={ inputRef } />; // input.ref becomes the internal useRef-object
the input component needs something it can attach itself to. E.g. a fictional implementation might look something like:
const input = function( props ){
const thisDomObject = thisDomObject();
if( props.ref ){
// input.ref.current becomes thisDomObject,
// input.ref is the internal useRef-object, so
// (useRef-object).current also becomes thisDomObject
props.ref.current = thisDomObject;
}
};
This would not work without the .current:
// input.ref was the internal useRef-object, and now becomes thisDomObject.
// The internal useRef-object stays unchanged.
props.ref = thisDomObject;
Remark:
I think the property name "current" is just a more or less arbitrarily choosen name that doesn't matter. The only important thing is, that it is a property.
May seem like a stupid question, but for example, if I create a ref and I store a boolean in it.
cont isTrue= useRef(false);
Whenever I access it, say to flip it, I need to do so via the .current.isTrue.current = !isTrue.current;
What is the .current property all about? I mean if it's the only property the ref ever has, why bother having the bit property at all?
Ref.current is null because the ref is not set till after the function returns and the content is rendered. The useEffect hook fires every time the value of contents of the array passed to it changes. By passing observed in the array and by passing a callback that logs observed to the console, the useEffect hook can be leveraged to accomplish your task.
useEffect(() => {
console.log(observed.current);
}, [observed]);
Edit: This only works on the first render as changes to a ref do not trigger re-renders. But the general statement about useEffect still holds. If you want to observe changes to a ref of a dom element, you can use a callback as ref.
<div
ref={el => { console.log(el); observed.current = el; }} // or setState(el)
className="App"
>
Code Sandbox
This is what I ended up doing since calling useEffect on rlvRef did not capture all events.
const rlvRef = useRef()
const [refVisible, setRefVisible] = useState(false)
useEffect(() => {
if (!refVisible) {
return
}
// detected rendering
}, refVisible)
return (
<RecyclerListView
ref={el => { rlvRef.current = el; setRefVisible(!!el); }}
...
/>
)
I know I am a little late, but since you don't seem to have accepted any of the other answers I'd figure I'd give it a shot too, maybe this is the one that helps you.
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Short answer, no.
The only things that cause a re-render in React are the following:
- A state change within the component (via the
useStateoruseReducerhooks) - A prop change
- A parent render (due to 1. 2. or 3.) if the component is not memoized or otherwise referentially the same (see this question and answer for more info on this rabbit hole)
Let's see what happens in the code example you shared:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Initial render
myRefgets set to{current: 1}- The effect callback function gets registered
- React elements get rendered
- React flushes to the DOM (this is the part where you see the result on the screen)
- The effect callback function gets executed, "myRef current changed" gets printed in the console
And that's it. None of the above 3 conditions is satisfied, so no more rerenders.
But what happens when you click the button? You run an effect. This effect changes the current value of the ref object, but does not trigger a change that would cause a rerender (any of either 1. 2. or 3.). You can think of refs as part of an "effect". They do not abide by the lifecycle of React components and they do not affect it either.
If the component was to rerender now (say, due to its parent rerendering), the following would happen:
Normal render
myRefgets set to{current: 1}- Set up of refs only happens on initial render, so the lineconst myRef = useRef(1);has no further effect.- The effect callback function gets registered
- React elements get rendered
- React flushes to the DOM if necessary
- The previous effect's cleanup function gets executed (here there is none)
- The effect callback function gets executed, "myRef current changed" gets printed in the console. If you had a
console.log(myRef.current)inside the effect callback, you would now see that the printed value would be 2 (or however many times you have pressed the button between the initial render and this render)
All in all, the only way to trigger a re-render due to a ref change (with the ref being either a value or even a ref to a DOM element) is to use a ref callback (as suggested in this answer) and inside that callback store the ref value to a state provided by useState.
use useCallBack instead, here is the explanation from React docs:
We didn’t choose useRef in this example because an object ref doesn’t notify us about changes to the current ref value. Using a callback ref ensures that even if a child component displays the measured node later (e.g. in response to a click), we still get notified about it in the parent component and can update the measurements.
Note that we pass [] as a dependency array to useCallback. This ensures that our ref callback doesn’t change between the re-renders, and so React won’t call it unnecessarily.
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}