Using forwardRef with proptypes and eslint
Asked Answered
Z

4

39

I am trying to use forwardRef for a Button in a project using eslint and prop-types.

This is what I tried so far, and the errors I get each time:

First attempt

function Button ({ action = { callback: () => {}, title: 'unknown' } }, ref) {
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
}

Button.propTypes = {
  action: Action.isRequired
}

export default forwardRef(Button)

This will give me the following warning in the console: Warning: forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?

Second attempt

function ButtonFunction ({ action = { callback: () => {}, title: 'unknown' } }, ref) {
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
}

const Button = forwardRef(ButtonFunction);

Button.propTypes = {
  action: Action.isRequired
}

export default ButtonFunction;

I get: action is missing in props validation.

Third attempt

const Button = forwardRef(({ action = { callback: () => {}, title: 'unknown' } }, ref) => {
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
});

Button.propTypes = {
  action: Action.isRequired
}

export default Button;

This time, I get: Component definition is missing display name.

So what is the correct way to do this?

Zante answered 13/1, 2020 at 11:52 Comment(0)
L
64

You are almost done with your third attempt. But you don't have to use twice forwardRef, the first use with the Button declaration is enough. The display name rule is not an error (neither at JavaScript nor React level), but rather an intentional prop in order to show the "real" name of the component, used by React in debugging messages. In your case, the forwardRef function will hide the "real" component name for the transpiler.

You can even disable this rule if it's a real problem to write displayName for each of those case.

https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md

const Button = forwardRef(({ action = { callback: () => {}, title: 'unknown' } }, ref) => {
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
});

Button.propTypes = {
  action: Action.isRequired
}

Button.displayName = 'Button'

export default Button
Laggard answered 17/1, 2020 at 15:56 Comment(4)
Sorry for the second forwardRef, I messed up in the copy paste between my different attempts... Thanks a lot for the answer!Zante
i tried 30min to adapt your answer, it work only if we put the same order as you do ! no Button.propTypes before const Button = forwardRef as we can do with normal function. thanks for answer :)Ehman
I had related issue. Started my component like this:Gi
For anyone else having problems with this approach removing VSCode proptype intellisense, add /** @type {React.ForwardRefRenderFunction<?, Button.propTypes> */ in the line above const Button = forwardRef(...). This also makes it so you can place propTypes at the top of the file without this forced order as @Alainlb explained. To understand why, see: #66553990Scone
G
6
const Form = React.forwardRef(function Form(
  { submitHandler, keyUpHandler, label, type, placeholder, buttonTxt },
  ref
) {

export default Form

No warnings doing this way. function Form takes care of the name.

Gi answered 5/3, 2021 at 18:15 Comment(1)
Can you please provide some explanation on how did this approach work?Outpour
E
2
interface SomeProps {
 // written your props
};

const SomeFC = forwardRef((props:SomeProps,ref)=>{
// do something and returns
}})

Declare the type of props for inner FC can fix the proptypes warnning!

Evalyn answered 5/4, 2021 at 3:54 Comment(0)
W
0

Your second attempt was actually correct, except for the fact that you exported the wrong thing:

function ButtonFunction({ action = { callback: () => {}, title: 'unknown' } }, ref)
{
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
}

const Button = forwardRef(ButtonFunction);

Button.propTypes = {
  action: Action.isRequired
}

// export default ButtonFunction; // <-- you exported the inner function
export default Button; // <-- you should export the wrapped function

My advice is to use the same name for both the function and for the const variable. This way you can not make the same mistake again. We can also shorten the syntax a bit.

const Button = forwardRef(function Button({ action = { callback: () => {}, title: 'unknown' } }, ref)
{
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
});

Button.propTypes = {
  action: Action.isRequired
}

export default Button;

If you are not using propTypes the syntax gets much shorter:

export default forwardRef(Button({ action = { callback: () => {}, title: 'unknown' } }, ref)
{
  return (<button ref={ref} onClick={action.callback} title={action.description} type="button">{action.icon || action.title}</button>)
})

I personally prefer to use TypeScript for validating parameters, since in that case I do not need propTypes and I can use the shortest syntax.

Without answered 22/6, 2023 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.