React-native: Get refs to ListView rows

Created on 17 Apr 2015  路  16Comments  路  Source: facebook/react-native

HI

I have a ListView and need to measure the layout of one row. I searched, but did not find a way to access a ListViews rows. ( ListView._visibleRows seems to be an empty object ).
Is there a way to do this?
If not I'd suggest to add a "ref" prop, based on the sectionID and rowID to every element created in, ListView.render ...

Locked

Most helpful comment

You should be able to put a function ref in renderRow:

render: function() {
  return (
    <ListView
      dataSource={this.state.dataSource}
      renderRow={(rowData) =>
        <Text ref={(row, sec, i) => this.rows[sec][i] = row}>{rowData}</Text>
      }
    />
  );
},

or similar.

All 16 comments

+1

One could just add a simple function that writes the ref of each list element to the _visibleRows property.

Proposal: ( part of the ListView's render function )

      for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {
        var rowID = rowIDs[rowIdx];
        var comboID = sectionID + rowID;
        var shouldUpdateRow = rowCount >= this.state.prevRenderedRowsCount &&
          dataSource.rowShouldUpdate(sectionIdx, rowIdx);
        var row =
          <StaticRenderer
            key={'r_' + comboID}
            ref={ function(rowRef) { this._visibleRows[comboID] = rowRef; } }
            shouldUpdate={!!shouldUpdateRow}
            render={this.props.renderRow.bind(
              null,
              dataSource.getRowData(sectionIdx, rowIdx),
              sectionID,
              rowID
            )}
          />;
        bodyComponents.push(row);
        totalIndex++;
        if (++rowCount === this.state.curRenderedRowsCount) {
          break;
        }
      }

+1

You should be able to put a function ref in renderRow:

render: function() {
  return (
    <ListView
      dataSource={this.state.dataSource}
      renderRow={(rowData) =>
        <Text ref={(row, sec, i) => this.rows[sec][i] = row}>{rowData}</Text>
      }
    />
  );
},

or similar.

@spicyj Thanks you directed me on the right path :+1:

This worked for me:

render: function() {
  return (
    <ListView
      dataSource={this.state.dataSource}
      renderRow={(rowData, sec, i) =>
        <Text ref={(row) => this.rows[sec][i] = row}>{rowData}</Text>
      }
    />
  );
},

Hey @lukasreichart getting stuck on this. How do you access the row once you set the ref. this.rows and this.props.rows, and this.props.refs all return undefined

@lukasreichart thanks to your code 馃憤
@arilitan you can access it with this.rows[sectionId][rowId] beacuse it's an array.
Or you can change
<Text ref={(row) => this.rows[sec][i] = row}>{rowData}</Text>
to
<Text ref={(row) => this.rows[i] = row}>{rowData}</Text>
and access it via this.rows[rowid].

I am getting an error

undefined is not an object (evaluating '_this2.rows')

<View ref={(rowData, sectionID, rowID) => this.rows[sectionID][rowID] = row} >

HI @senthilsivanath . I'm running in to the same issue, did you have any luck in resolving?

Hi @brianfoody and @senthilsivanath I am also running into the same issue. Were you able to resolve it?

No luck since @samdturner

@spicyj @lukasreichart - the solution with refs does not work for me all the time - specifically for "long" lists where children are not yet rendered (due to ListView optimalization). Is there a better way then setting high value of scrollRenderAheadDistance?

Sometimes a null gets supplied to a callback ref={r => // r is null}. So, I had to do something like ref={r => r && (rowsArray[rowId] = r) }, so that rows are not assigned null when that happens.

After trying the REFS way and it being not a very good approach, I noticed the onChangedVisibleRows params ( visibleRows and changedRows ) are hashes. They must have done this for a reason. It then made sense to assign the row rendered its sectionId and rowId and also an event emitter defined from the parent. The parent then does an emit change event when onChangedVisibleRows occurs, passing in visibleRows and changedRows. Since each row has props defined for sectionId and rowId, each row can then do a quick lookup in the hashes to see if its visible or not.

I have gone through all the solutions but the way i found working is this:

 constructor(props) {
        super(props);

        const ds = new ListView.DataSource({
            rowHasChanged: (r1, r2) => r1 !== r2
        });

        this.data = [
            {
                id: 1,
                title: 'Apple'
            }, {
                id: 2,
                title: 'Samsung'
            }
        ];

        this.rows = [];

        this.state = {
            selectedIndex: 0,
            dataSource: ds.cloneWithRows(this.data)
        };
    }

 render() {
        return (
            <View style={style.container}>
                {this.listView()}
            </View>
        );
    }

listView = () => {
        return (<ListView
            ref={(instance) => this.list = instance}
            dataSource={this.state.dataSource}
            renderRow={this.renderRow}/>);
    }

renderRow = (rowData, sectionID, rowID) => {
        return (<MenuSliderRow
            ref={(instance) => this.rows.push(instance)}
            key={rowID}
            selected={(this.state.selectedIndex == rowID)}
            title={rowData.title}
            onPress={() => this.onRowSelected(rowID)}/>);
    }

Later you can access this like

onRowSelected(rowID) {

        this.rows.map((row) => {
            row.setSelected(false);
        })

        const selectedRow = this.rows[rowID];
        selectedRow.setSelected(true);

        const rowData = this.data[rowID];
        this.setState({selectedIndex: rowID})
    }


Was this page helpful?
0 / 5 - 0 ratings