Refs to a React Element are never populated until the Element in question is mounted. So, the issue was that you were logging too early.
I have an example below running in a function component to demonstrate creating the refs and logging them once the element in question is mounted using useEffect
.
The other issue you might have is that based on the code I see, you might be creating the ref in the render function of a class component, which wouldn't work well because you wouldn't have access to the ref variable once it's actually rendered. Typically, you keep ref variable as an instance property of the class so you can access it when you need it.
For working with function components, you need to use forwardRef
on the function component as part of it's definition. The forwarded ref can go to the useImperativeHandle
hook, or to another element.
A little more info in React's Documentation on Accessing Refs:
When a ref is passed to an element in render, a reference to the node
becomes accessible at the current attribute of the ref.
const node = this.myRef.current;
The value of the ref differs depending on the type of the node:
When the ref attribute is used on an HTML element, the ref created in the constructor with React.createRef() receives the underlying DOM
element as its current property.
When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current.
You may not use the ref attribute on function components because they don’t have instances.
That last point is the key to note here: React.ForwardRef allows you to give the function components the ability to decide what a ref should do because otherwise the ref would be meaningless anyway.
In general in class components, if you want to pass a ref through it, you generally have to pass it down with a separate prop name. One of the ways shown here: How to use React.forwardRef in a class based component?
const { useRef, useEffect, useImperativeHandle } = React;
const TestFunction = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
shout: () => console.log("I'm Yelling over here!")
}));
return <div>TestFunction</div>;
});
class TestComponent extends React.Component {
testFunct = () => {
console.log("testFunct works!");
};
render() {
return <div>TestComponent</div>;
}
}
function App() {
const elementRef = useRef();
const element = <div>Test</div>;
const clonedElement = React.cloneElement(element, { ref: elementRef });
const classRef = useRef();
const classElement = <TestComponent />;
const clonedClass = React.cloneElement(classElement, { ref: classRef });
const functionRef = useRef();
const functionElement = <TestFunction />;
const clonedFunction = React.cloneElement(functionElement, {
ref: functionRef
});
useEffect(() => {
console.log('reference to an element',elementRef.current);
// This produces weird output in the stack overflow console.
// console.log(classRef.current);
console.log('function from a reference to a class component', classRef.current.testFunct);
classRef.current.testFunct();
console.log('reference to a function component',functionRef.current);
functionRef.current.shout();
});
return (
<div className="App">
{clonedElement}
{clonedClass}
{clonedFunction}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root" />