react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

Table components: Refs are not set in first render

Open jdharrisnz opened this issue 2 weeks ago โ€ข 4 comments

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

  1. If this is caused by Table's computation model introducing weird timing, look into adjusting that
  2. If 1 is not possible, document the behaviour and/or export a custom useTableRef hook 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

jdharrisnz avatar Dec 10 '25 21:12 jdharrisnz

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 avatar Dec 11 '25 01:12 nwidynski

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

jdharrisnz avatar Dec 11 '25 02:12 jdharrisnz

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.

nwidynski avatar Dec 11 '25 02:12 nwidynski

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?

jdharrisnz avatar Dec 11 '25 07:12 jdharrisnz

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.

nwidynski avatar Dec 11 '25 17:12 nwidynski