Search code examples
reactjsreact-hooksref

Expose state and method of child Component in parent with React


I know it's not a good pattern to do that, but you will understand why I want to do like that.

I have a HTable, which use a third-party library (react-table)

const HTable = <T extends object>({ columns, data, tableInstance}: Props<T>) {
   const instance: TableInstance<T> = useTable<T> (
   // Parameters
   )
   React.useImperativeHandle(tableInstance, () => instance);
}

Now, I want to control columns visibility from parent. I did:

const Parent = () => {
      const [tableInstance, setTableInstance] = React.useState<TableInstance<SaleItem>>();

      <Table data={data} columns={columns} tableInstance={(instance) => setTableInstance(instance)}

       return tableInstance.columns.map((column) => {
           <Toggle active={column.isVisible} onClick={() =>column.toggleHiden()}
       }
}

The column hides well, but the state doesn't update and neither does the toggle, and I don't understand why. Could you help me to understand?

EDIT:

Adding a sandbox.

https://codesandbox.io/s/react-table-imperative-ref-forked-dilx3?file=/src/App.js

Please note that I cannot use React.forwardRef, because I use typescript and React.forwardRef doesn't allow generic type like this if I use forwardRef

interface TableProps<T extends object> {
    data: T[],
    columns: Column<T>[],
    tableInstance?:  React.RefObject<TableInstance<T>>,
}

Solution

  • Your issue is that react-tables useTable() hook always returns the same object as instance wrapper (the ref never changes). So your parent, is re-setting tableInstance to the same object - which does not trigger an update. Actually most of the contained values are also memoized. To get it reactive grab the headerGroups property.

    const {
      headerGroups,
      ...otherProperties,
    } = instance;
    
    React.useImperativeHandle(
      tableInstance,
      () => ({ ...properties }), // select properties individually
      [headerGroups, ...properties],
    );