I have a Checkbox react ponent that has to support the indeterminate state, but I'm updating our ponents to forward refs properly. The checkbox ponent already uses a callback ref internally to set the indeterminate property. Here's the original ponent (simplified):
export type ICheckboxProps = {
checked?: boolean
indeterminate?: boolean
} & React.InputHTMLAttributes<HTMLInputElement>
export const Checkbox: React.FC<ICheckboxProps> = props => {
const { checked = false, ...rest } = props;
return (
<input
type="checkbox"
checked={checked}
ref={ref => {
if (ref) {
ref.indeterminate = !checked ? indeterminate! : false;
}
}}
{...rest}
/>
)
}
Now, since this is a UI library, I'm trying to forward a ref as well. But that clashes with the callback ref - now I have two separate refs. Additionally, the forwarded ref could be a callback ref. So I can't even access the instance there to set the indeterminate property. I've tried a bunch of stuff, but no matter what I do typescript's helpful red underlines tell me that I'm wrong.
How do I both apply the forwarded ref to the input and set the indeterminate property on the input?
Here's it most of the way but there's an issue noted:
export type ICheckboxProps = {
checked?: boolean
indeterminate?: boolean
} & React.InputHTMLAttributes<HTMLInputElement>
export const Checkbox = React.forwardRef<HTMLInputElement, ICheckboxProps>((props, inRef) => {
const { checked = false, ...rest } = props;
return (
<input
type="checkbox"
checked={checked}
ref={ref => {
if (ref) {
ref.indeterminate = !checked ? indeterminate! : false;
if (inRef) {
if (typeof inRef === "function") {
inRef(ref)
} else {
inRef.current = ref // Cannot assign to 'current' because it is a read-only property.
}
}
}
}}
{...rest}
/>
)
})
I have a Checkbox react ponent that has to support the indeterminate state, but I'm updating our ponents to forward refs properly. The checkbox ponent already uses a callback ref internally to set the indeterminate property. Here's the original ponent (simplified):
export type ICheckboxProps = {
checked?: boolean
indeterminate?: boolean
} & React.InputHTMLAttributes<HTMLInputElement>
export const Checkbox: React.FC<ICheckboxProps> = props => {
const { checked = false, ...rest } = props;
return (
<input
type="checkbox"
checked={checked}
ref={ref => {
if (ref) {
ref.indeterminate = !checked ? indeterminate! : false;
}
}}
{...rest}
/>
)
}
Now, since this is a UI library, I'm trying to forward a ref as well. But that clashes with the callback ref - now I have two separate refs. Additionally, the forwarded ref could be a callback ref. So I can't even access the instance there to set the indeterminate property. I've tried a bunch of stuff, but no matter what I do typescript's helpful red underlines tell me that I'm wrong.
How do I both apply the forwarded ref to the input and set the indeterminate property on the input?
Here's it most of the way but there's an issue noted:
export type ICheckboxProps = {
checked?: boolean
indeterminate?: boolean
} & React.InputHTMLAttributes<HTMLInputElement>
export const Checkbox = React.forwardRef<HTMLInputElement, ICheckboxProps>((props, inRef) => {
const { checked = false, ...rest } = props;
return (
<input
type="checkbox"
checked={checked}
ref={ref => {
if (ref) {
ref.indeterminate = !checked ? indeterminate! : false;
if (inRef) {
if (typeof inRef === "function") {
inRef(ref)
} else {
inRef.current = ref // Cannot assign to 'current' because it is a read-only property.
}
}
}
}}
{...rest}
/>
)
})
Share
Improve this question
edited Jul 30, 2019 at 20:30
Nathan Smith
asked Jul 30, 2019 at 18:37
Nathan SmithNathan Smith
1973 silver badges10 bronze badges
2 Answers
Reset to default 11The useImperativeHandle
hook does almost exactly the same as your second example. The assignment of ref.current
to inRef
is then handled internally by React, and you don't have to break the contract by changing the readonly property.
export const Checkbox = React.forwardRef<HTMLInputElement, ICheckboxProps>((props, inRef) => {
const { checked = false, indeterminate, ...rest } = props;
const ref = useRef<HTMLInputElement>(null)
useImperativeHandle(inRef, () => ref.current!, [ref])
return (
<input
type="checkbox"
checked={checked}
ref={ref}
{...rest}
/>
)
})
In typescript playground
I would like to make a ment on the non-null assertion on ref.current
. As far as I can tell, refs for children are resolved before refs for parents, but the only related statement in the documentation I could find is calling inputRef.current.focus()
without a null guard in the documentation of useImperativeHandle
You can do anything you want with the forwarded ref, including setting its current value:
const Checkbox = React.forwardRef(({ checked = false, indeterminate, ...rest }, forwardedRef) => (
<input
type="checkbox"
checked={checked}
ref={(inputElement) => {
if (inputElement) {
inputElement.indeterminate = !checked && indeterminate
}
if (forwardedRef) {
if(typeof(forwardedRef) === "function") {
forwardedRef(inputElement)
} else {
forwardedRef.current = inputElement
}
}
{...rest}
/>
))