Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref callback argument is null #8359

Closed
screendriver opened this issue Nov 20, 2016 · 16 comments
Closed

ref callback argument is null #8359

screendriver opened this issue Nov 20, 2016 · 16 comments
Assignees

Comments

@screendriver
Copy link

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.

@gaearon gaearon self-assigned this Nov 20, 2016
@gaearon
Copy link
Collaborator

gaearon commented Nov 20, 2016

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.

@gaearon
Copy link
Collaborator

gaearon commented Nov 20, 2016

We should warn about this by the way. You can track #7272 and #7267 for this.

@screendriver
Copy link
Author

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.

@gaearon
Copy link
Collaborator

gaearon commented Nov 20, 2016

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?

@screendriver
Copy link
Author

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?

@gaearon
Copy link
Collaborator

gaearon commented Nov 21, 2016

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.

@screendriver
Copy link
Author

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 🤔

@gaearon
Copy link
Collaborator

gaearon commented Nov 21, 2016

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.

@screendriver
Copy link
Author

Ah I see! Thank you Dan. I think I got it now 😎 👍

@gaearon
Copy link
Collaborator

gaearon commented Nov 21, 2016

Is there anything in the documentation that was missing to make it click?

@screendriver
Copy link
Author

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.

@gaearon
Copy link
Collaborator

gaearon commented Nov 21, 2016

As for the second part, is there something in the Conditional Rendering page that is missing? I think it includes some similar examples.

@screendriver
Copy link
Author

I think it includes some similar examples.

You are right. I read it now twice times and I think it is clear enough.

@catamphetamine
Copy link
Contributor

The correct behaviour would be not passing null to functional components but rather throwing an error explaining what's going on.

@gaearon
Copy link
Collaborator

gaearon commented Apr 8, 2017

We are printing a warning for this in master. It will be part of React 16.

@elektronik2k5
Copy link

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. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants