Definitelytyped: [styled-components] Cannot use forwardRef with styled component

Created on 14 Sep 2018  路  29Comments  路  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/styled-components package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @Igorbek @Igmat


Here is a simple repro. I can use forwarded ref on regular input, but not with styled one, it gives weird Typescript error, I am not sure what it means really.

https://codesandbox.io/s/o5ompq26j6

import * as React from 'react'
import styled from 'styled-components'

const StyledInput = styled.input`
  background-color: yellow;
`

export const WithInput = React.forwardRef<HTMLInputElement>((props, ref) => (
  <React.Fragment>
    <input ref={ref} />
    <StyledInput ref={ref} />
  </React.Fragment>
))
Type 'string | ((instance: HTMLInputElement | null) => any) | RefObject<HTMLInputElement> | undefined' is not assignable to type 'string | (string & ((instance: HTMLInputElement | null) => any)) | (string & RefObject<HTMLInputElement>) | (((instance: Component<ThemedOuterStyledProps<ClassAttributes<HTMLInputElement> & InputHTMLAttributes<HTMLInputElement> & { ...; }, any>, any, any> | null) => any) & string) | ... 5 more ... | undefined'.
  Type '(instance: HTMLInputElement | null) => any' is not assignable to type 'string | (string & ((instance: HTMLInputElement | null) => any)) | (string & RefObject<HTMLInputElement>) | (((instance: Component<ThemedOuterStyledProps<ClassAttributes<HTMLInputElement> & InputHTMLAttributes<HTMLInputElement> & { ...; }, any>, any, any> | null) => any) & string) | ... 5 more ... | undefined'.
    Type '(instance: HTMLInputElement | null) => any' is not assignable to type 'RefObject<Component<ThemedOuterStyledProps<ClassAttributes<HTMLInputElement> & InputHTMLAttributes<HTMLInputElement> & { invalid?: boolean | undefined; }, any>, any, any>> & RefObject<...>'.
      Type '(instance: HTMLInputElement | null) => any' is not assignable to type 'RefObject<Component<ThemedOuterStyledProps<ClassAttributes<HTMLInputElement> & InputHTMLAttributes<HTMLInputElement> & { invalid?: boolean | undefined; }, any>, any, any>>'.
        Property 'current' is missing in type '(instance: HTMLInputElement | null) => any'.

Most helpful comment

At the moment I didn't come up with a solution yet. It looked harder than I expected. The issue is that what we called StyledComponentClass is not a class component anymore it is a forwarding component that needs to carry information of what it was created for to correctly accept refs.

A reduced repro:

const StyledInput = styled.input` `;
const inputRef = React.createRef<HTMLInputElement>();

<StyledInput ref={inputRef} />; // error

Change would require adding new generic parameters and might be breaking.
I'll keep you updated.

All 29 comments

Really Really need this fixed to swap to 4.0.0 +1

@johnnyreilly Do you think you would able to help with this? Seems that original authors lost interest. There is a bunch of other issues related to styled-components and nobody paying attention to those. Or please ping someone who could help.

Feel free to submit at PR - this can be reviewed and merged to unblock you.

@johnnyreilly Sorry, but that's just the thing, I don't understand what's the issue. I would be happy to do a PR if I would only half understand why it's happening :(

I'll look into this today.

Any word on this? It's also blocking our upgrade of styled-components.

At the moment I didn't come up with a solution yet. It looked harder than I expected. The issue is that what we called StyledComponentClass is not a class component anymore it is a forwarding component that needs to carry information of what it was created for to correctly accept refs.

A reduced repro:

const StyledInput = styled.input` `;
const inputRef = React.createRef<HTMLInputElement>();

<StyledInput ref={inputRef} />; // error

Change would require adding new generic parameters and might be breaking.
I'll keep you updated.

Also blocking our upgrade of styled-components to v4 for a week now :<

Hi! Do I understand correctly that these problems are due to the fact that the authors of styled-components have mixed up the Component and ComponentClass classes?

For me, it's expecting either string & ((r: HTMLDivElement) => any or string & React.RefObject<HTMLDivElement> and the like. Every type passed requires it to be string & which doesn't look right. I should be able to just pass a RefObject or function.

image

My current workaround is to add as any.

ref={this.captureFn as any}

This seems to be a consequence of Ref including string in it. Maybe I
should just make a PR to rip the band-aid off and drop string from it,
it'll never work on anything but class components rendering class/host
components and is deprecated anyway.

Closing as with @types/styled-components: 4.1.7 this seems to be working correctly now. Thank you @Jessidhia for amazing work!

Closing as with @types/styled-components: 4.1.7 this seems to be working correctly now. Thank you @Jessidhia for amazing work!

Unfortunately for me this is not working yet. I'm still using the @cmrigney solution

Still borked for me as well.

typing-1

typing-2

"@types/styled-components": "^4.1.12",
"ts-lint": "^4.5.1",
"typescript": "^3.3.3333"
"styled-components": "^4.1.3"

Make sure you really have a some latest version of @types/react package as well. Especially with Yarn, I've got burned many times where there were different versions of that package was spread across causing havoc. The best cure AFAIK is to manually edit yarn.lock and remove all instances of @types/react and then reinstall.

"@types/react": "^16.8.7",

// node_modules/@types/react/package.json
"version": "16.8.7",

Problem still persists :(

@superhawk610 is there a node_modules/@types/styled-components/node_modules/@types/react/package.json?

Yeah, nice catch. It's on 16.8.6. However, removing that directory and replacing it with the updated types I previously mentioned doesn't fix the error.

@superhawk610 replacing single folder won't catch them all as there might be other diverged react types.

The problem stems from some type packages targeting dependencies with wildcard * version. This is quite common for React based libraries. For example @types/styled-components specifies @types/react as a dependency with version *. Package managers treat it as any version goes, so if you update @types/react, versions will diverge and @types/styled-components will stay on the older version @types/react, usually when it was first written to lockfile.

Do you use Yarn? You can search for @types/react@* in yarn.lock. This should be something like:

"@types/react@*", "@types/react@^16.8.7":
  version "16.8.7"
...

If you have another @types/react statement before or after this one, then you have multiple React types in repository.

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33015 - looks like it should be a peerdependency instead.

@superhawk610 I've found using the resolutions property in the package.json file very helpful for solving package version mismatches, specifically for @types/react.

I haven't had a chance to properly look into your issue, but if it is indeed a an issue of conflicting definitions, try adding the following to your package.json file.

"resolutions": {
  "@types/react": "16.8.7"
}

Thanks for the tips, everybody. I have a working solution, but it's still not pretty.

const ref = React.useRef() as React.MutableRefObject<HTMLImageElement>;
// ...
return <Image ref={ref} style={style} src={cat} />;

It seems that React.useRef<T>() returns a React.MutableRefObject<T | undefined> but SC's ref only accepts React.MutableRefObject<T>, not React.MutableRefObject<T | undefined>. The type assertion has it passing for now, and it appears this is off-topic from the original issue so I may open a new issue if I can get a minimal reproducible repo put together.

I ran into this today, I'm just not using ref for now.

e.g.

  export const Component: React.Fn<React.HTML<"tr">> = ({
    children,
    ref,
    ...props
  }) => <Wide {...props}>{children}</Wide>;

  const Wide = styled.div`
    padding: 1em 0;
    text-align: center;
  `;

For me the issue was simply using the wrong type on useRef. On an <a> element is used useRef<HTMLLinkElement>(null) instead of useRef<HTMLAnchorElement>(null).

This issue persists, unfortunately. The fix I found works but is verbose. I'm using React.forwardRef.

React.forwardRef((
  {...props},
  ref: ((instance: HTMLInputElement | null) => void) | React.MutableRefObject<HTMLInputElement | null> | null
) => {
  return <StyledInput ref={ref} />
})

This worked for me:

`
import React from 'react';
import styled from 'styled-components';

const ForwardRefComponent = React.forwardRef((props, ref) =>

);

const StyledComponent = styled(ForwardRefComponent)'
grid-column-start: 1;
grid-column-end: span 6;
position: relative;
height: 100%;
width: 100%;
';

export default StyledComponent;
`

My current workaround is to add as any.

ref={this.captureFn as any}

i wish this simple solution worked for me, but for some reason it does not.

using "react": "^16.13.0", "styled-components": "^5.0.0", "styled-components": "^5.0.0",

any ideas?

I see the issue is closed but even with very up to date version of the libraries the error persists for me.

I found a work around here, hope this help

const StyledInput = styled.input``

export const Input = forwardRef<HTMLInputElement, Omit<JSX.IntrinsicElements['input'], 'ref'>>(
  ({ ...props }, forwardedRef) => {
  console.log('forwardedRef', forwardedRef) // inputRef

  return <StyledInput ref={forwardedRef} {...props} />
})
Was this page helpful?
0 / 5 - 0 ratings