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

[styled-components] Cannot use forwardRef with styled component #28884

Closed
3 tasks done
danielkcz opened this issue Sep 14, 2018 · 41 comments
Closed
3 tasks done

[styled-components] Cannot use forwardRef with styled component #28884

danielkcz opened this issue Sep 14, 2018 · 41 comments

Comments

@danielkcz
Copy link
Contributor


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'.
@ShanonJackson
Copy link

Really Really need this fixed to swap to 4.0.0 +1

@danielkcz
Copy link
Contributor Author

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

@johnnyreilly
Copy link
Member

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

@danielkcz
Copy link
Contributor Author

@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 :(

@Igorbek
Copy link
Collaborator

Igorbek commented Oct 18, 2018

I'll look into this today.

@aMoniker
Copy link
Contributor

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

@Igorbek
Copy link
Collaborator

Igorbek commented Oct 22, 2018

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.

@ShanonJackson
Copy link

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

@khusamov
Copy link

khusamov commented Nov 2, 2018

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?

@cmrigney
Copy link

cmrigney commented Nov 2, 2018

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

@cmrigney
Copy link

cmrigney commented Nov 2, 2018

My current workaround is to add as any.

ref={this.captureFn as any}

@Jessidhia
Copy link
Member

Jessidhia commented Nov 3, 2018 via email

@danielkcz
Copy link
Contributor Author

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

@vctormb
Copy link

vctormb commented Feb 15, 2019

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

@superhawk610
Copy link
Contributor

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"

@danielkcz
Copy link
Contributor Author

danielkcz commented Mar 10, 2019

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.

@superhawk610
Copy link
Contributor

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

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

Problem still persists :(

@msokk
Copy link
Contributor

msokk commented Mar 10, 2019

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

@superhawk610
Copy link
Contributor

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.

@msokk
Copy link
Contributor

msokk commented Mar 10, 2019

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

#33015 - looks like it should be a peerdependency instead.

@alex-ketch
Copy link
Contributor

@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"
}

@superhawk610
Copy link
Contributor

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.

sophiebits added a commit to sophiebits/react that referenced this issue Apr 5, 2020
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and still allow you to omit the ref object from the deps list.

Test Plan: unit tests
sophiebits added a commit to sophiebits/react that referenced this issue Apr 5, 2020
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and allows you to still omit the ref object from the deps list.

Test Plan: unit tests
sophiebits added a commit to sophiebits/react that referenced this issue Apr 5, 2020
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and allows you to still omit the ref object from the deps list.

Test Plan: unit tests
sophiebits added a commit to sophiebits/react that referenced this issue Apr 5, 2020
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and allows you to still omit the ref object from the deps list.

Test Plan: unit tests
sophiebits added a commit to facebook/react that referenced this issue Apr 5, 2020
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and allows you to still omit the ref object from the deps list.

Test Plan: unit tests
@cruhl
Copy link

cruhl commented May 6, 2020

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

@cruhl
Copy link

cruhl commented May 6, 2020

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;
  `;

@thisismydesign
Copy link

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

@cezaraugusto
Copy link

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} />
})

@jc-development
Copy link

This worked for me:

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

const ForwardRefComponent = React.forwardRef((props, ref) => <section {...props} ref={ref} />);

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

export default StyledComponent;
`

@hems
Copy link

hems commented Oct 2, 2020

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.

@6thpath
Copy link

6thpath commented Nov 3, 2020

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} />
})

@monolithed
Copy link
Contributor

@lPaths, the problem is still there

Снимок экрана 2021-01-07 в 2 41 41

Can somebody explain why is the issue closed?

@superhawk610
Copy link
Contributor

superhawk610 commented Jan 7, 2021

@monolithed The typings should now be correct. I threw together a small sandbox to illustrate: https://codesandbox.io/s/styled-components-ref-example-dt1wk.

@colemars
Copy link

colemars commented Feb 8, 2021

This is still broken afaict

@davisk4rpi
Copy link
Contributor

Something similar was happening for me with styled-components/native

Type 'ForwardedRef<TextInput>' is not assignable to type 'Ref<TextInput>'.
      Type '(instance: TextInput) => void' is not assignable to type 'Ref<TextInput>'.

The issue went away once I upgraded @types/styled-components from 5.1.4 to 5.1.7

@DavidLozzi
Copy link

got this issue with

    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "styled-components": "^4.4.1"

My styled component is handling these params to remove the React warnings we get (following)

export const SearchInput = styled(({ expandedWidthPx, ...rest }) => <Input {...rest} />)`
      ...
      width: ${({ expandedWidthPx }) => expandedWidthPx - 180}px
`;

Warning: React does not recognize the expandedWidthPx prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase expandedwidthpx instead. If you accidentally passed it from a parent component, remove it from the DOM element.

But when implementing the code I get the forwardRef warning

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()

So I moved on to something like this:

const ForwardRefInput = React.forwardRef((props, ref) => <Input {...props} ref={ref} />);
export const SearchInput = styled(ForwardRefInput)`

but then I get the same initial warning React does not recognize the expandedWidthPx prop on a DOM element
I also tried

const ForwardRefInput = React.forwardRef((props, ref) => <Input {...props} ref={ref} />);
export const SearchInput = styled(({ expandedWidthPx, ...rest }) => <ForwardRefInput {...rest} />)`

and this gives the same Function components cannot be given refs. error again. So I've come full circle....

@DavidLozzi
Copy link

Ha, taking a mental break worked this time, this now works, no console errors

const ForwardRefInput = React.forwardRef(({ showTransition, expandedWidthPx, ...props }, ref) => <Input {...props} ref={ref} />);
export const SearchInput = styled(ForwardRefInput)`

@atahrijouti
Copy link

atahrijouti commented Oct 21, 2021

I had a similar error, I think you might need a type for your ref in the component argument 👍🏼

({children, ...props}, ref: React.Ref<HTMLInputElement>) => (
    // ....
)

@lPaths, the problem is still there

Снимок экрана 2021-01-07 в 2 41 41

Can somebody explain why is the issue closed?

@dgattey
Copy link

dgattey commented Dec 1, 2021

@6thpath + @abderrahmane-tj have it right! Thanks you two. This worked perfectly, with typed Props and two layers of styled-component nesting:

interface Props {
  // Just an example prop
  value: boolean;
  ...
}

const Input = styled.input`
  ...
`

// The base `styled.input` can be wrapped and itself styled as many times as needed
const StyledInput = styled(Input)`
  ...
`

// This defines use of Props like normal, but also defines our ref
const Checkbox = (
  {
 value,
    ...rest
  }: Props,
  ref: React.Ref<HTMLInputElement>,
): React.ReactElement => <StyledInput ref={ref} value={value} {...rest} />

// This is the magic - you need to omit the ref from the input type here, and also include your Props
export default React.forwardRef<
  HTMLInputElement,
  Props & Omit<JSX.IntrinsicElements['input'], 'ref'>
>(Checkbox);

@azmi989
Copy link

azmi989 commented Dec 2, 2021

yes this cleared the error but unfortunately when I try to access ref.current its always null

here is the styled component
code-snapshot1

this is a wrapper component that gets the default props and compare it to the custom props, so nothing fancy here
code-snapshot2

types for the container component that extends component props
code-snapshot3

finally here i consumed the container component and passed the ref, and this is where im stocked, ref.current always null
can somebody help please
code-snapshot4

gfox1984 pushed a commit to gfox1984/eslint-plugin-granular-hooks that referenced this issue Mar 18, 2022
Sometimes you need to use casts, eg: DefinitelyTyped/DefinitelyTyped#28884 (comment). This change ignores them and allows you to still omit the ref object from the deps list.

Test Plan: unit tests
@robinclaes
Copy link

robinclaes commented Apr 25, 2022

Currently also seeing this with styled-components/native after upgrading react-native

Type 'ForwardedRef<TextInput>' is not assignable to type 'Ref<TextInput> | undefined'.

react-native: 0.67.4
@types/react-native: 0.67.6
styled-components: 5.3.5
@types/styled-components-react-native: 5.1.3 // depends on @types/styled-components: latest

@davisk4rpi you mentioned you were able to fix this?

@kopax-polyconseil
Copy link

kopax-polyconseil commented Oct 4, 2022

Hello, did you find how to use createRef<View>() with styled-components/native @robinclaes ? styled-components/styled-components#3823

It seems broken to me

react-native 0.68.2
styled-components/native 5.3.3
@types/styled-components-react-native 5.1.3
typescript 4.8.3

@matthew-dean
Copy link
Contributor

matthew-dean commented Nov 30, 2022

forwardRef still fails as it types a component as props: StyledComponentPropsWithAs<keyof IntrinsicElements | ComponentType<{}>

Because the typing is broad, other components assigned, such as <button> props, force a ref error. The props are fine, and TypeScript understands the type of the element, but it seems Styled Components infers the ref prop incorrectly.

Hovering over ref={ref} gives a TS error like:

Types of parameters 'instance' and 'instance' are incompatible.
            Type 'SVGViewElement' is missing the following properties from type 'HTMLButtonElement': disabled, form, formAction, formEnctype, and 37 more

Note, my component doesn't allow SVGViewElement, but I guess assigning 'as' to the component infers a broad type.

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

No branches or pull requests