2

I am trying to pass the ref of my Table.row into a click handler. Perhaps I want to select a row and highlight it when selected by changing the className. However it always logs null.

Attempt:

<Table.Body>
    {_.map(data, ({ Name, Version, Type, Modified }) => (
        <Table.Row
          ref={(ref) => { this.row = ref; }}
          className=""
          key={Version}
          onClick={() => this.handleClick(this.row, Name, Version, Type, Modified)}
        >
            <Table.Cell>{Name}</Table.Cell>
            <Table.Cell>{Version}</Table.Cell>
            <Table.Cell>{Type}</Table.Cell>
            <Table.Cell>{Modified}</Table.Cell>
        </Table.Row>
    ))}
</Table.Body>

handleClick(row, Name, Version, Type, Modified) {
    const me = this;
    log.info('logging row', row); //null
    log.info('logging row data', Name, Version, Type, Modified); //works
}

Does anybody know why ref is not working properly here?

Oleksandr Fediashov
  • 4,315
  • 1
  • 24
  • 42
user2456977
  • 3,830
  • 14
  • 48
  • 87
  • It should work, though what you are doing wrong is reassigning the `this.ref` on each iteration through your map. As such, the last `Table.Row` should be referenced by `this.ref`, the other rows should have no ref. Perhaps the error is related? – Chris Jan 16 '18 at 22:42
  • you are overriding the ref by the way – Sagiv b.g Jan 16 '18 at 22:43
  • another thing, i don't see any support for `refs` in their [docs](https://react.semantic-ui.com/collections/table). you might find [this pattern](https://github.com/Semantic-Org/Semantic-UI-React/issues/895#issuecomment-261844519) helpful. – Sagiv b.g Jan 16 '18 at 22:48
  • i have a better and more react'ish solution for you. i'll write it as an answer – Sagiv b.g Jan 16 '18 at 23:16
  • that would be amazing - still stuck here – user2456977 Jan 16 '18 at 23:20

2 Answers2

2

As i mentioned in my comments, I don't see any support for refs in their docs.
And as for your ref implementation, you are overriding this.row on each iteration.

Anyway there is a more react'ish solution for this in my opinion.

If you want to keep track of which rows were selected you will need an object in your state that will get updated on each click of a row, a kind of a lookup table object.

To create such lookup table, you will need id's for each row (you can use an id from the data object or an index of the array).

In order to be able pass down the id to the Table.Row and back up to the parent as a parameter of the onClick you can wrap the Table.Row with a class component that will handle the clicks and the data that it returns.

For example MyRow will expect to get a rowId, a onClick event and of course the active props that we will pass down to Table.Row.

when MyRow will get clicked, it will pass the this.props.rowId as a parameter to the parent, and the parent will update the lookup table.

Here is a running example of this use case:

const { Table } = semanticUIReact; // --> import { Table } from 'semantic-ui-react'

const usersFromServer = [
  { name: 'john', age: 25, gender: 'male', id: 1 },
  { name: 'jane', age: 22, gender: 'female', id: 2 },
  { name: 'david', age: 31, gender: 'male', id: 3 },
  { name: 'jain', age: 29, gender: 'female', id: 4 }
];


class MyRow extends React.Component {
  onClick = (e) => {
    const { onClick, rowId } = this.props;
    onClick(rowId, e);
  }

  render() {
    const { data, active } = this.props;
    return (
      <Table.Row onClick={this.onClick} active={active}>
        <Table.Cell>{data.name}</Table.Cell>
        <Table.Cell>{data.age}</Table.Cell>
        <Table.Cell >{data.gender}</Table.Cell>
      </Table.Row>
    );
  }
}


class TableExample extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeRows: []
    }
  }

  onRowClick = (id, e) => {
    const { activeRows } = this.state;
    const nextRows = {
      ...activeRows,
      [id]: !activeRows[id]
    }
    this.setState({ activeRows: nextRows });
  }

  render() {
    const { activeRows } = this.state;
    return (
      <Table unstackable>
        <Table.Header>
          <Table.Row>
            <Table.HeaderCell>Name</Table.HeaderCell>
            <Table.HeaderCell>Age</Table.HeaderCell>
            <Table.HeaderCell>Gender</Table.HeaderCell>
          </Table.Row>
        </Table.Header>

        <Table.Body>
          {
            usersFromServer.map((u) => {
              const isActive = activeRows[u.id];
              return (
                <MyRow
                  active={isActive}
                  key={u.id}
                  rowId={u.id}
                  data={u}
                  onClick={this.onRowClick}
                />
              );
            })
          }

        </Table.Body>
      </Table>
    )
  }
}

ReactDOM.render(<TableExample />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.9/semantic.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/semantic-ui-react@0.77.2/dist/umd/semantic-ui-react.min.js"></script>
<div id="root"></div>
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • thanks so much for your answer.. have not had time yet to test it but the solution I came up with is much simpler -- take a look what do you think – user2456977 Jan 17 '18 at 00:05
  • there are a lot of solutions for this challenge, but the best practice is to create another component and use the "component composition pattern". – Sagiv b.g Jan 17 '18 at 00:27
  • @Sagivb.g what if we want to scroll to a given row? I'd expect to be able to take the ref and do `ref.current.scrollIntoView()`. – GACy20 Aug 03 '21 at 10:38
0

Not a very React-ish solution but very simple: Instead of using refs I made a unique id for every row. Then in the clickHandler I can identify the row and mark it selected:

export default class TableExampleSortable extends Component {

    constructor(props) {
        super(props);

        this.state = {
            column: null,
            data: props.convo,
            direction: null,
        };

        this.handleClick = this.handleClick.bind(this);
    }


    handleSort = clickedColumn => () => {
        const { column, data, direction } = this.state;

        if (column !== clickedColumn) {
            this.setState({
                column: clickedColumn,
                data: _.sortBy(data, [clickedColumn]),
                direction: 'ascending',
            });

            return;
        }

        this.setState({
            data: data.reverse(),
            direction: direction === 'ascending' ? 'descending' : 'ascending',
        });
    }

    handleClick(Version) {
        const element = document.getElementById(Version);
        if (element.className === 'selected') {
            element.className = 'notSelected';
        } else {
            element.className = 'selected';
        }
    }

    render() {
        const { column, data, direction } = this.state;

        return (
            <div>
                <Table sortable selectable>
                    <Table.Header>
                        <Table.Row>
                            <Table.HeaderCell sorted={column === 'Name' ? direction : null} onClick={this.handleSort('Name')}>
                                Name
                            </Table.HeaderCell>
                            <Table.HeaderCell sorted={column === 'Version' ? direction : null} onClick={this.handleSort('Version')}>
                                Version
                            </Table.HeaderCell>
                            <Table.HeaderCell sorted={column === 'Type' ? direction : null} onClick={this.handleSort('Type')}>
                                Type
                            </Table.HeaderCell>
                            <Table.HeaderCell sorted={column === 'Modified' ? direction : null} onClick={this.handleSort('Modified')}>
                                Modified
                            </Table.HeaderCell>
                        </Table.Row>
                    </Table.Header>
                    <Table.Body>
                        {_.map(data, ({ Name, Version, Type, Modified }) => (
                            <Table.Row
                              id={Version}
                              className="notSelected"
                              key={Version}
                              onClick={() => this.handleClick(Version)}
                            >
                                <Table.Cell>{Name}</Table.Cell>
                                <Table.Cell>{Version}</Table.Cell>
                                <Table.Cell>{Type}</Table.Cell>
                                <Table.Cell>{Modified}</Table.Cell>
                            </Table.Row>
                        ))}
                    </Table.Body>
                </Table>
            </div>
        );
    }
}
user2456977
  • 3,830
  • 14
  • 48
  • 87
  • this pattern may produce a performance hit. you are creating a new function reference on each iteration which will run on each render call. not to mention this is not much of a scalable nor testable solution. simple is not always better :) you should read [this answer](https://stackoverflow.com/a/29810951/3148807) and [this lint rule](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md) for the differences in the patterns – Sagiv b.g Jan 17 '18 at 00:13
  • edited the full class -- my function is outside the render. take a look. It is NOT being called on every render – user2456977 Jan 17 '18 at 00:18
  • seems like you went with [my answer](https://stackoverflow.com/a/45448802/3148807) (currying) from the link i provided. if you will look closely i wrote in the bottom of my answer: "_Note that this approach doesn't solve the creation of a new instance on each render_". you are still creating a new function because the outer function is returning a new function every time it gets invoked. there is no way to bypass this without creating another `class` component that will create an instance and let react do it's magic with the props and diffing algorithm – Sagiv b.g Jan 17 '18 at 00:21
  • caught me with the arrow functions in the onClick.. thanks for you answer! – user2456977 Jan 18 '18 at 16:43