Table components: Refs are not set in first render
Provide a general summary of the issue here
When using refs with Table components, the data isn't set on first render. Minimal example:
import { useEffect, useRef } from "react"
import {
Cell,
Column,
Row,
Table,
TableBody,
TableHeader,
} from "react-aria-components"
import type { ColumnProps } from "react-aria-components"
const MyColumn = (props: ColumnProps) => {
const ref = useRef<HTMLTableCellElement>(null)
useEffect(() => {
console.log(ref) // Logs "{ current: null }"
}, [])
return <Column ref={ref} {...props} />
}
const Component = () => (
<Table>
<TableHeader>
<MyColumn>Hello world</MyColumn>
</TableHeader>
<TableBody>
<Row>
<Cell>A cell</Cell>
</Row>
</TableBody>
</Table>
)
There is a slightly hacky workaround that works. React state setters satisfy the RefCallback type, so you can just make it reactive and do this:
const MyColumn = (props: ColumnProps) => {
const [ref, setRef] = useState<HTMLTableCellElement | null>(null)
useEffect(() => {
console.log(ref)
}, [ref])
return <Column ref={setRef} {...props} />
}
But if this is the solution, it should really be mentioned in the docs or supported explicitly with a custom useTableRef hook.
๐ค Expected Behavior?
Refs should be available in effects on first render.
๐ฏ Current Behavior
Ref is null on first render.
๐ Possible Solution
- If this is caused by Table's computation model introducing weird timing, look into adjusting that
- If 1 is not possible, document the behaviour and/or export a custom
useTableRefhook to help with it
๐ฆ Context
Standard ref usage with Table components doesn't work, and we need strange workarounds to fit with it.
๐ฅ๏ธ Steps to Reproduce
https://codesandbox.io/p/devbox/zealous-joana-l7dvvy
Version
"react-aria-components": "^1.13.0"
What browsers are you seeing the problem on?
Chrome, Safari
If other, please specify.
No response
What operating system are you using?
macOS 26.1
๐งข Your Company/Team
No response
๐ท Tracking Issue
No response
This is expected because the first render pass is used to build the collection from the JSX tree. Is there anything preventing you from using a callback ref?
@nwidynski You mean like in the useState workaround I mentioned? Like I said it feels hacky and I'm not super comfortable with it, but it works for my purposes.
If you mean regular callback ref, what I needed couldn't be done that way, because I had other variables that needed to be included in the trigger that I could handle only with useEffect - so more than just mount/unmount.
Yeah, I was talking about a regular callback ref, but if it got further dependencies after mount that's though. You could try and bypass that by building a hash out of your dependencies and use that as the key to the underlying column. If the hash changes, it will automatically re-invoke the callback ref you passed to the column.
Another alternative would be to move the useEffect into a component rendered inside the column, not outside, if that's possible. That or you remain with the forced re-render you currently got ๐
PS: This is due to the way all collections in react-aria are constructed and not specific to the table. Tbh, I don't think the core maintainers would consider this issue a bug. If you need customization of collections, at some point you will need to drop to the hook level for it to be straightforward again. Documentation for this behavior can be found here.
Thanks for your advice. Yeah, I'm really pushing on the limit of wrapper components for Table, maybe I need to switch to hooks.
Do you think it would be worth documenting the ref behaviour and offering solutions?
Yeah, I think documentation around collections could be improved. I've actually got a solution pending in https://github.com/adobe/react-spectrum/pull/8523#issuecomment-3172901451, which could be expanded to support itemRef injection. Maybe i can get some additions to the documentation squeezed in as well.