React-virtualized: change columnWidth does not reset column width on Grid

Created on 29 Feb 2016  路  16Comments  路  Source: bvaughn/react-virtualized

Dear developers,

i am trying to change column width after initial render of Grid, once i try to do so i don't see it happening unless componentWillUnmount method is called on parent component and parent is re-render again. i can see columnWidth prop is being pass correct to Grid Component which i can notice in chrome dev tool as well.

Reason i see is left property of css in unchanged once we change column width . For e.g

Initial Grid Render has

Col 1 - Width : 20 left 0
Col 2 - Width : 50 left 20
Col 3 - Width : 50 left 70

Once i change Col2 Width to 100 , Col 3 left is unchanged.
Col 1 - Width : 20 left 0
Col 2 - Width : 100 left 20
Col 3 - Width : 50 left 70

Col 3 left property is unchanged in Grid.js

is this a bug ?

question

Most helpful comment

That method is a public method on Grid (recomputeGridSize) as well as FlexTable and VirtualScroll )(recomputeRowHeights).

To use the method you'll need a ref to your grid (or whatever you're using). Something like...

export default class YourComponent extends Component {
  componentWillUpdate (nextProps, nextState) {
    // Check for your update condition however you need to...
    if (nextProps.someProperty !== this.props.someProperty) {
      this.grid.recomputeGridSize()
    }
  }

  render () {
    // Do whatever you need to do here...
    return (
      <Grid
        ref={this._setRef}
        {...otherProps}
      />
    )
  }

  _setRef (ref) {
    this.grid = ref
  }
}

All 16 comments

Hi @pkumar84,

This is actually mentioned in the docs (see recomputeGridSize). Basically if you're passing a function to determine the width/height of cells, Grid doesn't have a reliable way to determine when the underlying data/cell has changed size. In that event, this method lets you tell it explicitly that it needs to remeasure.

Thanks for quick response. do you mean <Grid ref="columnGrid" ... >
columnGrid.recomputeGridSize(); ? it should be called on compoentWillUpdate() of parent componet ? quickly tried, but does not work.

That method is a public method on Grid (recomputeGridSize) as well as FlexTable and VirtualScroll )(recomputeRowHeights).

To use the method you'll need a ref to your grid (or whatever you're using). Something like...

export default class YourComponent extends Component {
  componentWillUpdate (nextProps, nextState) {
    // Check for your update condition however you need to...
    if (nextProps.someProperty !== this.props.someProperty) {
      this.grid.recomputeGridSize()
    }
  }

  render () {
    // Do whatever you need to do here...
    return (
      <Grid
        ref={this._setRef}
        {...otherProps}
      />
    )
  }

  _setRef (ref) {
    this.grid = ref
  }
}

Going to close this issue for now because I think it's answered but we can continue to talk here if you'd like additional help. :)

Thank you @bvaughn, where to define

this.grid = ref ?

Parent component State or props ?

I often set refs as instance properties on my components (not in this.props _or_ this.state but just as this._myRef or similar). But you could also just do this if you feel it would be more comfortable:

export default class YourComponent extends Component {
  componentWillUpdate (nextProps, nextState) {
    // Check for your update condition however you need to...
    if (nextProps.someProperty !== this.props.someProperty) {
      this.refs.Grid.recomputeGridSize()
    }
  }

  render () {
    // Do whatever you need to do here...
    return (
      <Grid
        ref='Grid'
        {...otherProps}
      />
    )
  }
}

Now that I think about it, in your specific situation, this is probably a better approach anyway. :)

Not sure what i am doing wrong but seems like ref i can only access if it is defined on html element, not on react component example below.

<ScrollSync>
               {({onScroll, scrollLeft}) =>
                  <div className="col flex-1">
                        <AutoSizer disableHeight>
                           {({width}) =>
                              <Grid  ref="columnGrid" className="overflow-hidden" height={30} rowHeight={30} columnsCount={columnList.size + 1} rowsCount={1} {...{scrollLeft, width, columnWidth}} renderCell={renderHeader}/>
                           }
                        </AutoSizer>
                     <div className="flex-1">
                        <AutoSizer>
                           {({width, height}) =>
                              <Grid ref="tableGrid" rowHeight={30} columnsCount={columnList.size + 1} rowsCount={sortedPatients.size} {...{onScroll, scrollLeft, width, height, renderCell, columnWidth}} />
                           }
                        </AutoSizer>
                     </div>
                  </div>
               }
            </ScrollSync>
componentWillUpdate() {
      console.log(' WILLLLLLL UPDATEEEE ');
      if (this.refs && this.refs.columnGrid && this.refs.tableGrid) {
        // this.refs.columnGrid.recomputeGridSize();
        // this.refs.tableGrid.recomputeGridSize();
      }

         //this.refs ? this.refs.columnGrid ? this.refs.columnGrid.recomputeGridSize() : null : null;
      //this.grid.recomputeGridSize();
     // this.refs ? this.refs.dataGrid ? this.refs.dataGrid.recomputeGridSize() : null : null;
   }

this.refs.columnGrid is undefined .

BTW, how you declare instance variable inside react class component ?

Afraid you're going to need to tidy up the formatting before I can give you much input.

sorry for inconvenience. just formatted.

Still a little unclear, but it looks like ... what you have should work?

Not sure why this.ref.columnGrid and this.ref.tableGrid is undefined .

BTW, how you will declare instance variable in React Component?

I can't really venture a guess without seeing a more full code example. What you've pasted above wouldn't compile.

Instance variables within a class (or an instantiated function) work like this.variableName = 'somevalue'

import React, {PropTypes} from 'react';
import PureComponent from 'react-pure-render/component';
import {action} from 'actions/AppActions';
import * as AT from 'actions/Types';
import {sortByInfo, Icon} from 'utils/core';
import {List, Map, OrderedMap, Set, Iterable} from 'immutable';
import {ScrollSync, AutoSizer, Grid} from 'react-virtualized';

export default class extends PureComponent {

   static propTypes = {
      dispatch: PropTypes.func.isRequired,
      patients: PropTypes.instanceOf(Iterable).isRequired,
      selectedPatientIds: PropTypes.instanceOf(Set).isRequired,
      columns: PropTypes.instanceOf(OrderedMap).isRequired,
      patientsSortInfo: PropTypes.instanceOf(List).isRequired,
      registry: PropTypes.instanceOf(Map)
   };

   resize(width, name) {
      console.log(':::: ' + width + ' :::  ' + name);
      const {dispatch} = this.props;
      dispatch(action(AT.RESIZE_PATIENTS_COLUMN, {columnId: name, width: width + 100}));
   }

   componentWillUpdate() {
      console.log(' WILLLLLLL UPDATEEEE ');
      if ( this.refs && this.refs.columnDataGrid && this.refs.tableDataGrid ) {
         this.refs.columnDataGrid.recomputeGridSize();
         this.refs.tableDataGrid.recomputeGridSize();
      }
   }
   render() {
      const {dispatch, patients, selectedPatientIds, columns, patientsSortInfo} = this.props;
      const sortedPatients = sortByInfo(patients.valueSeq(), patientsSortInfo).toList();
      const columnList = columns.valueSeq().toList();
      const renderHeader = ({columnIndex}) => (
         <div style={{height: '100%'}} className="row align-items-center background-gray-1 border-right-gray-2 phs border-bottom-gray-2">
            {columnIndex === 0 ?
               <input id="selectAllPatients" type="checkbox" checked={selectedPatientIds.size === sortedPatients.size} onChange={e => dispatch(action(AT.TOGGLE_ALL_PATIENTS_SELECTION, e.target.checked))}/>
            :
               () => {
                  const column = columnList.get(columnIndex - 1);
                  const columnId = column.get('columnId');
                  const columnWidth = columnIndex === 0 ? 25 : columnList.getIn([columnIndex, 'width']) || 250;
                  const [i, sort] = patientsSortInfo.findEntry(s => s.get('name') === columnId) || [null, null];
                  return (
                     <div width={columnWidth + 10 } className="row">
                        <div className="flex-1" onClick={() => dispatch(action(AT.CHANGE_PATIENTS_SORT, !sort ? patientsSortInfo.push(Map({name: columnId, dir: 1})) : sort.get('dir') === 1 ? patientsSortInfo.setIn([i, 'dir'], -1) : patientsSortInfo.delete(i)))}>
                           {columnList.getIn([columnIndex - 1, 'name'])} {sort && (<Icon name={sort.get('dir') === 1 ? 'arrow_up' : 'arrow_down'}/>)}
                        </div>
                        <div ref="resizeHandler" className="resize-handler" onClick={ () => this.resize(columnWidth, columnId)} />
                     </div>
                  );
               }()
            }
         </div>
      );
      const renderCell = ({columnIndex, rowIndex}) => {
         const patient = sortedPatients.get(rowIndex);
         const pid = patient.get('PATIENTID');
         const columnWidth = columnIndex === 0 ? 25 : columnList.getIn([columnIndex, 'width']) || 250;

         return (
            <div style={{height: '100%', width: columnWidth}} className={`row align-items-center phs ${rowIndex % 2 ? 'background-gray-0' : ''}`}>
               {columnIndex === 0 ?
                  <input type="checkbox" id={pid + '_check'} checked={selectedPatientIds.has(pid)} onChange={() => dispatch(action(AT.TOGGLE_PATIENT_SELECTION, pid))}/>
               :
                  () => {
                     const column = columnList.get(columnIndex - 1);
                     const columnId = column.get('columnId');
                     const value = patient.get(columnId);
                     return columnId === 'sticky_key' ?
                        <button id={'noteIcon_' + pid} className="link ellipsis" title={value} onClick={() => dispatch(action(AT.OPEN_QUICK_NOTE_MODAL, pid))}>
                           {value || <Icon name="notes"/>}
                        </button>
                     : columnId === 'DISPLAY_NAME' ?
                        <button className="link" onClick={() => dispatch(action(AT.OPEN_PATIENT_PROFILE_MODAL, pid))}>{value}</button>
                     : !column.get('oneToMany') ?
                        <span style={{color: 'ff0000'}} onMouseOver= {(event) => dispatch(action(AT.SHOW_TOOLTIP, Map({showToolTip: true, toolTipXPosition: event.clientX, toolTipYPosition: event.clientY, value})))} onMouseOut= { () => dispatch(action(AT.HIDE_TOOLTIP, Map({showToolTip: false})))}>{column.get('dataType') === 'NUMBER' && column.get('sigFigs') && value ? value.toFixed(column.get('sigFigs')) : value }</span>
                     :
                        value + ' - ';
                  }()
               }
            </div>
         );
      };
      const columnWidth = (columnIndex) => {
       //  console.log(columnList.getIn([columnIndex, 'columnId']) + ' ::::: HEADER ::::: ' + columnList.getIn([columnIndex, 'width']));
         return (columnIndex === 0 ? 25 : columnList.getIn([columnIndex, 'width']) || 250);
      };
      return (
            <ScrollSync>
               {({onScroll, scrollLeft}) =>
                  <div className="col flex-1">
                        <AutoSizer disableHeight>
                           {({width}) =>
                              <Grid  ref="columnDataGrid" className="overflow-hidden" height={30} rowHeight={30} columnsCount={columnList.size + 1} rowsCount={1} {...{scrollLeft, width, columnWidth}} renderCell={renderHeader}/>
                           }
                        </AutoSizer>
                     <div className="flex-1">
                        <AutoSizer>
                           {({width, height}) =>
                              <Grid ref="tableDataGrid" rowHeight={30} columnsCount={columnList.size + 1} rowsCount={sortedPatients.size} {...{onScroll, scrollLeft, width, height, renderCell, columnWidth}} />
                           }
                        </AutoSizer>
                     </div>
                  </div>
               }
            </ScrollSync>
      );
   }
}

Ah, right. So the Grids being returned aren't declared as direct children (in the same was as the ScrollSync). They're being created and returned by child-functions. I don't know if string refs work under that setting.

But you could use the callback approach I originally suggested. :)

Basically you'd update the render function like so...

   <AutoSizer disableHeight>
      {({width}) =>
         <Grid ref={this._setColumnDataGrid} className="overflow-hidden" height={30} rowHeight={30} columnsCount={columnList.size + 1} rowsCount={1} {...{scrollLeft, width, columnWidth}} renderCell={renderHeader}/>
      }
   </AutoSizer>
<div className="flex-1">
   <AutoSizer>
      {({width, height}) =>
         <Grid ref={this._setTableDataGrid} rowHeight={30} columnsCount={columnList.size + 1} rowsCount={sortedPatients.size} {...{onScroll, scrollLeft, width, height, renderCell, columnWidth}} />
      }
   </AutoSizer>

Then add 2 class methods to receive the grids:

_setColumnDataGrid (ref) {
   this._columnDataGrid = ref
}

_setTableDataGrid (ref) {
   this._tableDataGrid = ref
}

And then you could update your componentWillUpdate function like so:

componentWillUpdate() {
   this._columnDataGrid.recomputeGridSize()
   this._tableDataGrid.recomputeGridSize()
}

Disclaimer I have not tested the above code, but I think it should more or less work. If not, it might be a good time for us to move this discussion to Stack Overflow :)

Thanks @bvaughn , i see what was wrong, if i use arrow method, it works. i.e.

ref={(ref) => this._setTableDataGrid(ref)}

Ah, a context issue. Gotcha. Glad you've got it working. :)

An alternate way of doing that (that I often end up doing, but didn't include in my example) is to bind such functions in the constructor:

constructor (props, state) {
  super(props, state)

  this._setColumnDataGrid = this._setColumnDataGrid.bind(this)
  this._setTableDataGrid = this._setTableDataGrid.bind(this)
}

Glad you've got it working though :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pkumar84 picture pkumar84  路  4Comments

davidychow87 picture davidychow87  路  3Comments

hyeminHwang picture hyeminHwang  路  3Comments

djeeg picture djeeg  路  3Comments

EdBoucher picture EdBoucher  路  3Comments