4

I am trying to use react-table with useRowSelect, based on the tutorial. I have a REST app running at /associate but can't work out how to get the "selectedFlatRows[].original" data out of the table. Having two tables in one form also complicates things.

function App() {
  //...

  function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData(event.target);

    fetch('/associate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: data,
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <Styles>
        <Table columns={tcsColumns} data={tcsList} />
        <Table columns={planColumns} data={planList} />
        <br />
        <button>Associate!</button>
      </Styles>
    </form>
  );
}

How do I access my two tables' data?

Pedro Baptista Afonso
  • 1,158
  • 2
  • 10
  • 18
djb
  • 1,635
  • 3
  • 26
  • 49
  • why don't you wrap each table inside a separate form? from what I've seen on the tutorial link, they use `useTable` hook to get `selectedFlatRows` variable for each table, so in your form handler, you also need to have at least 2 `selectedFlatRows` for each so you can access the `.original` data – Duc Hong Nov 18 '19 at 14:01
  • actually in this case I dont think you need a `
    `, just wrap each table like a separate component and having its own state (like `selectedFlatRows`), then passing the "submit"-like handler to each, this should work
    – Duc Hong Nov 18 '19 at 14:05
  • I'm not a react expert, so maybe there's things I don't understand, but I wasn't able to make Table into a component, because hooks don't work in classes. Also, my "associate" function requires the selection from both tables, so I can't delegate submit to each Table. That's why I was hoping to get an ID onto each Table, so I could get the Table object using standard DOM and call a getSelectedFlatRows() function. – djb Nov 18 '19 at 14:24

1 Answers1

6

I've put together a quick modified version from your tutorial link. You can have a look here: https://codesandbox.io/s/tannerlinsleyreact-table-row-selection-h3z4h?fontsize=14&hidenavigation=1&theme=dark

Basically, what I did was:

  • Passing a function handler as onSelectedRows prop to each table. Inside each <Table /> , use useEffect hook to call this function whenever selectedFlatRows changes. (It seems like react-table utilize hook 100%, so I cannot find their document on using with class, but no worries, this still doesn't affect your app, as long as you use React 16.8+, you're fine)
function Table({ onSelectedRows, columns, data }) {
  // Use the state and functions returned from useTable to build your UI
  const {
    ...,
    selectedFlatRows,
  } = useTable(
    {
      columns,
      data
    },
    useRowSelect
  );

  React.useEffect(() => {
    onSelectedRows(selectedFlatRows);
  }, [selectedFlatRows]);

  ...
}

From the parent scope, add a state to hold these selected rows. Then simply add a Button with onClick event handler to check the selected rows:

function App() {
  ...
  const [selectedRows, setSelectedData] = React.useState([]);
  const onSelectedRows = rows => {
    const mappedRows = rows.map(r => r.original);
    setSelectedData([...selectedRows, ...mappedRows]);
  };

  const onSubmitHandler = e => {
    e.preventDefault();
    console.log("submit: ", selectedRows);
  };

   return (
    <Styles>
      <button onClick={onSubmitHandler}>SUBMIT</button>
      <Table onSelectedRows={onSelectedRows} columns={columns} data={data} />
      <Table onSelectedRows={onSelectedRows} columns={columns2} data={data2} />
    </Styles>
  );
}

The takeaway point here is, you can pass a function to a component via prop, and use the values from component to handle on this parent scope. As what onSelectedRows did, here it receives a list of selected rows from table. I just do a naive solution to merge array and quick check. In real world scenario, you need to check for duplicate or added rows to skip it.

If your <App /> is a class-based component, It would look something like this: (notice on this.onSelectedRows, this.onSubmitHandler)

class App extends React.Component {
   state = {
      selectedRows: [],
   };

   const onSelectedRows = rows => {
    const mappedRows = rows.map(r => r.original);
    this.setState( prevState => {
       return {
        // your merged row array
       }
    })
  };

   const onSubmitHandler = e => {
     e.preventDefault();
     console.log("submit: ", selectedRows);
  };

   render() {
     return (
       <Styles>
         <button onClick={this.onSubmitHandler}>SUBMIT</button>
         <Table onSelectedRows={this.onSelectedRows} columns={this.columns} data={this.data} />
         <Table onSelectedRows={this.onSelectedRows} columns={this.columns2} data={this.data2} />
       </Styles>
     )
   }
}

You dont need a <form /> wrapper to be specifically calling submit as usual web browser. In your scenario, the data is quite simple so just a onClick event handler is enough.

Hope this help

Duc Hong
  • 1,149
  • 1
  • 14
  • 24
  • Thanks for all that work. I'm getting an infinite loop after adding your code snippets. But I'll start over from your example and add my code back step by step, and hopefully will work out what's wrong. – djb Nov 20 '19 at 09:27
  • 1
    the sample codebase won't run smoothly in your real codebase, try to look at the codesandbox sample link carefully and understand the logic applied, usually infinite loop happens because you update the state itself right in `useEffect( function, [ state ] )`. As you see, once you selected a row, `selectedFlatRows` will be updated automatically by `useTable`, once it's updated, `useEffect` detects this change and call `onSelectedRows` which is passed from ``, that life cycle is safe and doesn't make any infinite loop. Hope this help – Duc Hong Nov 20 '19 at 11:07
  • Ok I will keep looking at it. My code is practically identical so pretty confusing. Btw, there is a functional issue too: selecting and unselecting a checkbox still adds that row to the selectedRows. – djb Nov 20 '19 at 11:13
  • 1
    yes, that would be expected. As I've mentioned in the last part, the function `onSelectedRows` only receives the selected rows from the table. It doesn't know which rows they are or which table they are from. You have to validate those data before adding valid row to the final array. – Duc Hong Nov 20 '19 at 11:43
  • @DucHong I've tried getting this to work, using the table from a class-base component that contains state for some non-pertinent stuff to this but also one array for the rows selected in each table. However, following the code in your link as well as what was posted here I inevitably end up exceeding maximum update depth as when useEffect is called, then so is a method calling setState and so begins the cascade. Is it possible to work around somehow? Here's an almost cohesive commit related to this https://github.com/humlab/video_reuse_detector/commit/1854a630c1f4373eecf7df569452b9958b24dc81 – Filip Allberg Dec 04 '19 at 19:24
  • @Filip Allberg I've just quick checked your code and see that you're watching changes of `selectedFlatRows` and then calling `setSelectedRows` to update itself again, that's the reason why you got infinitive loop. There's no work around because that means there's something wrong in your logic. In my example I use a `` component as a parent to hold the selected rows value, in your case it seems like you're trying to update directly this value from a child component, which is totally different scenario. – Duc Hong Dec 05 '19 at 05:50
  • @DucHong Right, I'll see if I can't resolve it. The logic doesn't have to be expressed this way, the key is just perculating the selection to the parent. I'm presuming the logic might be flawed as I've tried looking at your approach as well as https://spectrum.chat/react-table/general/v7-how-get-selected-rows-outside-of-react-table~2deb9558-c484-4b97-9e1c-6be608f1f275 and maybe ended up making a few mistakes as a result. Thank you. – Filip Allberg Dec 05 '19 at 08:43