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
[@types/react] RefObject.current should no longer be readonly #31065
Comments
A PR would be appreciated! It should be a very easy one line change 😊 |
@ferdaber, I have gone through node_modules/@types/react/index.d.ts and there is the following code at line no 61 which declare current as readonly.
if this is the issue, then I can solve. can you guide for my first PR? |
Yes, it's just removing the |
|
They should be unified, the React runtime has no limitation on the |
It's not. It'a intentionally left readonly to ensure correct usage, even if it's not frozen. Refs initialized with null without specifically indicating you want to be able to assign null to it are interpreted as refs you want to be managed by React -- i.e. React "owns" the current and you're just viewing it. If you want a mutable ref object that starts with a null value, make sure to also give Perhaps this is easier for me to understand as I have worked with pointer-based languages often before and ownership is very important in them. And that's what refs are, a pointer. |
That's fair, I've been using |
We could modify |
Would that work for those who don't have |
🤔 Should we really make it easier to write incorrect code for those that do have |
When I created this issue I didn't realize there already was this |
Since the issue itself was based on |
I do wonder though if this overload pattern will pan out well, looking at the hooks documentation we see a few uses of |
Would it make more sense for the return value to be mutable if an initial value is given, but immutable if it is omitted? If an initial value is given, then the ref probably isn't going to be passed to a component via |
That's kind of what I was thinking. @Kovensky what are your thoughts? |
@ferdaber Refs that are passed to a component should be initialized with The problem in React's documentation and using Anyway, the way I defined |
Ah, looking more closely shows that, and it's interesting that it's initialized as |
I think it is fine the way it is, just a bit odd that whether you include the |
It's generally not, and at least in the past when I pointed that out in some piece of documentation the React team said that "even if it works" it's not intended to be ommitted. It's why the first argument to |
@ferdaber that's because they're not caring about types there. What should the type of Now you find yourself the |
// you should always give an argument even if the docs sometimes miss them
// $ExpectError
const ref1 = useRef()
// this is a mutable ref but you can only assign `null` to it
const ref2 = useRef(null)
// this is also a mutable ref but you can only assign `undefined`
const ref3 = useRef(undefined)
// this is a mutable ref of number
const ref4 = useRef(0)
// this is a mutable ref of number | null
const ref5 = useRef<number | null>(null)
// this is a mutable ref with an object
const ref6 = useRef<React.CSSProperties>({})
// this is a mutable ref that can hold an HTMLElement
const ref7 = useRef<HTMLElement | null>(null)
// this is the only case where the ref is immutable
// you did not say in the generic argument you want to be able to write
// null into it, but you gave a null anyway.
// I am taking this as the sign that this ref is intended
// to be used as an element ref (i.e. owned by React, you're only sharing)
const ref8 = useRef<HTMLElement>(null)
// not allowed, because you didn't say you want to write undefined in it
// this is essentially what would happen if we allowed useRef with no arguments
// to make it worse, you can't use it as an element ref, because
// React might write a null into it anyway.
// $ExpectError
const ref9 = useRef<HTMLElement>(undefined) |
Yeah this DX works for me, and the documentation is A++ so I don't think most people will get confused on it. Thanks for this elaboration! Honestly you should just permalink this issue in the typedoc :) |
There could be a case for supporting The problem is... what happens if you don't give a generic argument, which is allowed? TypeScript will just infer |
I'm getting this error with the following code: // ...
let intervalRef = useRef<NodeJS.Timeout>(null); // also tried with const instead of let
// ...
useEffect( () => {
const interval = setInterval( () => { /* do something */}, 1000);
intervalRef.current = interval; // In this line I'm getting the error
return () => {
clearInterval(intervalRef.current);
}
})
// ... And when I remove the interface RefObject<T> {
readonly current: T | null;
} I'm new with both reack hooks and typescript (just trying them out together) so my code could be wrong |
By default if you create a ref with a const intervalRef= useRef<NodeJS.Timeout | null>(null) // <-- the generic type is NodeJS.Timeout | null |
@ferdaber Thanks for that! |
Taking this a stage further and looking at the return type of Carrying on from the excellent example list in #31065 (comment), if I write:
what should the type of If we defined the types and functions as follows:
that would produce the following usages and types:
Is anything there wrong? For the answer to "what is the type of an unparameterised |
I added the overload with |null specifically as a convenience overload for DOM/component refs, because they always start null, they are always reset to null on unmount, and you never reassign the current yourself, only React. The readonly is there more to guard against logic errors than a representation of true JavaScript frozen object / getter-only property. You can only make it by accident when you both give a generic argument that says you do not accept null while initializing the value to null anyway. Any other case will be mutable. |
Ah, yes, I see now that What do we need to do with (And the |
Did another comment about forwardRef components get created? Essentially, you can't forward a ref and change it's current value directly, which I think is part of the point of forwarding the ref. |
I created the following hook:
And I get an error: "Cannot assign to 'current' because it is a read-only property." |
For that, you're going to have to cheat.
Yes, this is a bit unsound, but it's the only case I can think of where this kind of assignment is deliberate and not an accident -- you're replicating an internal behavior of React and thus have to break the rules. |
thanks a lot |
should be:
|
Please fill in this template. - [x] Use a meaningful title for the pull request. Include the name of the package modified. - [x] Test the change in your own code. (Compile and run.) - [ ] Add or edit tests to reflect the change. (Run with `npm test`.) - [x] Follow the advice from the [readme](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request). - [x] Avoid [common mistakes](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes). - [ ] Run `npm run lint package-name` (or `tsc` if no `tslint.json` is present). Select one of these and delete the others: If changing an existing definition: - [x] Provide a URL to documentation or source code which provides context for the suggested changes: DefinitelyTyped#31065, DefinitelyTyped#39062 - [x] If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header. - [x] If you are making substantial changes, consider adding a `tslint.json` containing `{ "extends": "dtslint/dt.json" }`. If for reason the any rule need to be disabled, disable it for that line using `// tslint:disable-next-line [ruleName]` and not for whole package so that the need for disabling can be reviewed. Fixes DefinitelyTyped#39062
Please fill in this template. - [x] Use a meaningful title for the pull request. Include the name of the package modified. - [x] Test the change in your own code. (Compile and run.) - [ ] Add or edit tests to reflect the change. (Run with `npm test`.) - [x] Follow the advice from the [readme](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request). - [x] Avoid [common mistakes](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes). - [ ] Run `npm run lint package-name` (or `tsc` if no `tslint.json` is present). Select one of these and delete the others: If changing an existing definition: - [x] Provide a URL to documentation or source code which provides context for the suggested changes: DefinitelyTyped#31065, DefinitelyTyped#39062 - [x] If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header. - [x] If you are making substantial changes, consider adding a `tslint.json` containing `{ "extends": "dtslint/dt.json" }`. If for reason the any rule need to be disabled, disable it for that line using `// tslint:disable-next-line [ruleName]` and not for whole package so that the need for disabling can be reviewed. Fixes DefinitelyTyped#39062
Please fill in this template. - [x] Use a meaningful title for the pull request. Include the name of the package modified. - [x] Test the change in your own code. (Compile and run.) - [x] Add or edit tests to reflect the change. (Run with `npm test`.) - [x] Follow the advice from the [readme](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request). - [x] Avoid [common mistakes](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes). - [x] Run `npm run lint package-name` (or `tsc` if no `tslint.json` is present). Select one of these and delete the others: If changing an existing definition: - [x] Provide a URL to documentation or source code which provides context for the suggested changes: DefinitelyTyped#31065, DefinitelyTyped#39062 - [x] If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header. - [x] If you are making substantial changes, consider adding a `tslint.json` containing `{ "extends": "dtslint/dt.json" }`. If for reason the any rule need to be disabled, disable it for that line using `// tslint:disable-next-line [ruleName]` and not for whole package so that the need for disabling can be reviewed. Fixes DefinitelyTyped#39062
Please fill in this template. - [x] Use a meaningful title for the pull request. Include the name of the package modified. - [x] Test the change in your own code. (Compile and run.) - [x] Add or edit tests to reflect the change. (Run with `npm test`.) - [x] Follow the advice from the [readme](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request). - [x] Avoid [common mistakes](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes). - [x] Run `npm run lint package-name` (or `tsc` if no `tslint.json` is present). Select one of these and delete the others: If changing an existing definition: - [x] Provide a URL to documentation or source code which provides context for the suggested changes: DefinitelyTyped#31065, DefinitelyTyped#39062 - [x] If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header. - [x] If you are making substantial changes, consider adding a `tslint.json` containing `{ "extends": "dtslint/dt.json" }`. If for reason the any rule need to be disabled, disable it for that line using `// tslint:disable-next-line [ruleName]` and not for whole package so that the need for disabling can be reviewed. Fixes DefinitelyTyped#39062
Please fill in this template. - [x] Use a meaningful title for the pull request. Include the name of the package modified. - [x] Test the change in your own code. (Compile and run.) - [x] Add or edit tests to reflect the change. (Run with `npm test`.) - [x] Follow the advice from the [readme](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request). - [x] Avoid [common mistakes](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes). - [x] Run `npm run lint package-name` (or `tsc` if no `tslint.json` is present). Select one of these and delete the others: If changing an existing definition: - [x] Provide a URL to documentation or source code which provides context for the suggested changes: #31065, #39062 - [x] If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header. - [x] If you are making substantial changes, consider adding a `tslint.json` containing `{ "extends": "dtslint/dt.json" }`. If for reason the any rule need to be disabled, disable it for that line using `// tslint:disable-next-line [ruleName]` and not for whole package so that the need for disabling can be reviewed. Fixes #39062
Does that mean the docs here are outdated? Because they clearly describe this as an intended workflow, not internal behavior. |
The docs in that case is referring to a ref that you own, but for refs that you pass as a function Component() {
// same API, different type semantics
const countRef = useRef<number>(0); // not readonly
const divRef = useRef<HTMLElement>(null); // readonly
return <button ref={divRef} onClick={() => countRef.current++}>Click me</button>
} |
My bad, should've scrolled up further. Got an error about Anyway, thank you very much for the explanation! |
I'm unclear if the I use a popular navigation library for React Native (React Navigation). In its documentation it commonly calls Should the type for React Native be different? See: https://reactnavigation.org/docs/navigating-without-navigation-prop |
@sylvanaar |
Summarising what I just read: The only way to set a value to a ref created by |
TLDR: If your initial value is |
@sylvanaar / @Imperyall, I couldn't get typescript to be happy with
|
I came to this issue because I got same error when using functions as a type. This was my code:
Solution was to wrap function into parenthesis, like this: |
I was trying to handle React Navigation's Navigating without the navigation prop initialization as well, I ended up manually assigning the type like so since the type is surely going to be either a boolean or null. export const isReadyRef: React.MutableRefObject<boolean | null> = React.createRef() |
Hi thread, we're moving DefinitelyTyped to use GitHub Discussions for conversations the To help with the transition, we're closing all issues which haven't had activity in the last 6 months, which includes this issue. If you think closing this issue is a mistake, please pop into the TypeScript Community Discord and mention the issue in the |
It is now okay to assign to
ref.current
, see example: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variablesTrying to assign a value to it gives the error:
Cannot assign to 'current' because it is a constant or a read-only property.
@types/react
package and had problems.Definitions by:
inindex.d.ts
) so they can respond.The text was updated successfully, but these errors were encountered: