Do you want to request a feature or report a bug?
A bug 馃悰
What is the current behavior?
The ref callback gets called with null
as argument instead of an instance of the component. This only occurs when you write the component name starting with an uppercase character (PascalCase). If you write the component starting with a lowercase character (camelCase) everything works as expected.
This jsfiddle demonstrates how you get a null
argument: https://jsfiddle.net/1v9vk4zb/
And this demonstrates how you get a correct reference: https://jsfiddle.net/a0d0owru/
As you can see I just renamed Button
to lowercaseButton
.
What is the expected behavior?
It should always pass the component instance as argument to the callback.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
This occurs in v15.4.0
and v15.3.1
. I didn't tested any other versions.
The lowerbaseButton
example doesn't use your component. Please read the relevant documentation. Your components should always start with an upper case letter if you use JSX.
The Button
example also works correctly. From the ref docs:
You may not use the
ref
attribute on functional components because they don't have instances.
So if you want to use refs, you need to define Button
as a class.
We should warn about this by the way. You can track #7272 and #7267 for this.
Hi Dan,
thank you for the quick response. I know that my components should start with an upper case and I read the docs before I opened this issue. My example should only illustrate the issue that ref
in my callback was null.
I believe I misunderstood something there. I know that I can't use this syntax in stateless components like it's written in the example in the ref docs but I didn't know that I also can't use it when I consume stateless components.
So is there any workaround or something else that I can do if I want the ref
callback on stateless components but don't have the ability to change or modify the component that I consume? For example I import a third party library or style from external npm packages.
I know that I can't use this syntax in stateless components like it's written in the example in the ref docs
What example are you referring to? I think ref docs actually show an example of using it in stateless components which is supported:
You can, however, use the ref attribute inside the render function of a functional component:
function CustomTextInput(props) { // textInput must be declared here so the ref callback can refer to it let textInput = null; function handleClick() { textInput.focus(); } return ( <div> <input type="text" ref={(input) => { textInput = input; }} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
As for
but I didn't know that I also can't use it when I consume stateless components.
I think this is also what docs say explicitly:
You may not use the ref attribute on functional components because they don't have instances.
"On", not "in". Maybe we can rephrase it to make it clearer?
So is there any workaround or something else that I can do if I want the ref callback on stateless components but don't have the ability to change or modify the component that I consume?
Can you explain what you expect to get from a ref? With class components, you get the instance so that you could call methods on it. With functional components, there are no instances and there are no methods on them. What do you expect to get from such ref?
Ok, I give you a more realistic example of what my current problem was. I write a React component that has different conditions what render()
should return and depending on what returned from render()
it should do something in componentDidMount()
import MyComponent from './MyComponent';
import MySecondComponent from './MySecondComponent';
import ThirdPartyComponent from 'some-npm-package';
class Container extends React.Component {
componentDidMount() {
if (this.myComponent) {
// do something
} else if (this.mySecondComponent) {
// do something
} else if (this.thirdPartyComponent) {
// do something
}
}
render() {
if (foo()) {
return <MyComponent ref={comp => this.myComponent = comp} />;
}
if (bar()) {
return <MySecondComponent ref={comp => this.mySecondComponent = comp} />;
}
return <ThirdPartyComponent ref={comp => this.thirdPartyComponent = comp} />;
}
}
this.thirdPartyComponent
is in this case always null because it is a stateless component. I didn't knew that before that it is a stateless component. I found it out by looking at the source code of the third party library.
My current "workaround" is to set a boolean flag instead of setting the ref instance: <ThirdPartyComponent ref={comp => this.thirdPartyComponent = true} />
So I don't need the ref
instance by itself. I only need to know which component was rendered. Is my "workaround" the right way to accomplish this?
This looks a bit like using React backwards. What do foo()
and bar()
really look like? Normally you would just repeat the same conditions in componentDidMount
. If they are, for some reason, expensive to compute, you can put something like visibleComponent: 'first'
in the state, recompute it when necessary, and use it in both methods. Either way, refs seem to be a wrong solution because you don't really need the reference.
foo()
and bar()
are really expensive to compute and it takes a lot of time in some circumstances so it does not make sense to do the same thing in componentDidMount
.
If they are, for some reason, expensive to compute, you can put something like visibleComponent: 'first' in the state
My first thought was like yours: I put it in the state. But this means I have to call setState()
in my render()
method and that's not allowed. So I thought I can workaround this by checking which component is rendered.
Either way, refs seem to be a wrong solution because you don't really need the reference.
Yes, I think I have a thought mistake but I don't know the most elegant solution here at the moment 馃
But this means I have to call setState() in my render() method and that's not allowed.
No, why? Compute them in constructor when you initialize the state. If they depend on props, also recompute them in componentWillReceiveProps.
Ah I see! Thank you Dan. I think I got it now 馃槑 馃憤
Is there anything in the documentation that was missing to make it click?
Well first it was, at least for me, not clear enough that I can't use ref
with stateless components. It was clear that stateless components itself can't use ref
but I thought I can consume stateless components like any other. So following example was clear to me:
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
But I thought I can use <CustomTextInput ref={input => this.input = input} />
from the outside. I don't know if I misunderstood You may not use the ref attribute on functional components because they don't have instances.
or oversight it.
Second: I think a little example for conditional rendering would be great that explains how to check if some components are rendered or not. So like my issue at the moment. I have some heavy calculation that decides if a component should rendered or not.
As for the second part, is there something in the Conditional Rendering page that is missing? I think it includes some similar examples.
I think it includes some similar examples.
You are right. I read it now twice times and I think it is clear enough.
The correct behaviour would be not passing null
to functional components but rather throwing an error explaining what's going on.
We are printing a warning for this in master. It will be part of React 16.
Any update on this? I've just been bitten by this gotcha in [email protected]
. I know: it's my fault and I should have RTFM, but a warning sure would have helped in this case. :smile:
Most helpful comment
The
lowerbaseButton
example doesn't use your component. Please read the relevant documentation. Your components should always start with an upper case letter if you use JSX.The
Button
example also works correctly. From the ref docs:So if you want to use refs, you need to define
Button
as a class.