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 ?
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 :)
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...