Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace render functions with components #496

Closed
kvet opened this issue Nov 21, 2017 · 12 comments
Closed

Replace render functions with components #496

kvet opened this issue Nov 21, 2017 · 12 comments
Assignees
Labels
enhancement Grid The DevExtreme Reactive Grid component

Comments

@kvet
Copy link
Contributor

kvet commented Nov 21, 2017

Starting with the initial release, our components and plugins contain "...Template" properties, which hold render functions. During alpha releases, we added many features providing rich customization capabilities. Now, many of those render functions are treated as React components: they can have children and an associated key property.
 
The Grid wraps each render function in the TemplateRenderer component to support passing an associated key property to this function. This causes creation of multiple TemplateRenderer components in the virtual DOM tree, which decreases performance. We are going to fix this problem. However, it will lead to a breaking change.
 

Suggested Change

 

  • use ...Component properties that accept a component instead of ...Template properties that accept render functions
  • support component overriding for a specific column for some properties (for example, tableCellComponent in TableView)
     

Difference between the Component and Render Function

 
Here is a property receiving a component:
 

<DataTypeProvider
  type="uppercase"
  valueFormatterComponent={ValueFormatter}
/>

const ValueFormatter = ({ value }) => {
  return <span>{value.toUpperCase()}</span>
}

 
and here is a render function:
 

<DataTypeProvider
  type="uppercase"
  // NOTE: it is possible to inline child component render
  valueFormatterRender={({ value }) => {
    return <span>{value}</span>
  }}
/>

 
If you use a property receiving a component, DataTypeProviter uses React.createElement to create a new React element based on the provided component. It means that if you pass a function to the component attribute, a new component is created each time the Grid is rendered. This causes redundant mounting and unmounting of the component instead of just updating it.
 

Passing a Render Function as a Component

 
We don't recommend passing a rendering function to a component property because of the issue described above. However, you can work around this limitation using any state management library. For example, Redux.
 
If you want to pass the rendering function to a component property and do not want to use a state management library, we advise you to use the following solution to avoid component unmounting.
 
The original code:
 

// NOTE: we need rows property in the following component,
// but it is not passed by React Grid
const TableRow = ({ children, row, rows }) => (
  <tr
    onClick={() => alert(rows.indexOf(row))}
  >
    {children}
  </tr>
);

export default class Demo extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      columns: [/* ... */],
      rows: [/* ... */],
    };
  }
  render() {
    const { rows, columns } = this.state;

    return (
      <Grid
        rows={rows}
        columns={columns}
      >
        <TableView
          tableRowComponent={TableRow}
        />
        <TableHeaderRow />
      </Grid>
    );
  }
}

 
Possible solution:
 

// NOTE: this function returns a memoized component
// that does not unmount component when render function is changed
const createRenderComponent = () => {
  let storedRender = () => null;
  const components = new Set();
  class RenderComponent extends React.Component {
    componentWillMount() {
      components.add(this);
    }
    componentWillUnmount() {
      components.delete(this);
    }
    render() {
      return storedRender(this.props);
    }
  }
  return (render) => {
    storedRender = render;
    Array.from(components.values())
      .forEach(component => component.forceUpdate());
    return RenderComponent;
  };
};

const TableRow = ({ children, row, rows }) => (
  <tr
    onClick={() => alert(rows.indexOf(row))}
  >
    {children}
  </tr>
);

export default class Demo extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      columns: [/* ... */],
      rows: [/* ... */],
    };
    
    // NOTE: you should create memoized render component in the constructor
    // in order to store it for several render method call
    this.rowRenderComponent = createRenderComponent();
  }
  render() {
    const { rows, columns } = this.state;

    return (
      <Grid
        rows={rows}
        columns={columns}
      >
        <TableView
          // NOTE: we pass rows to TableRow component
          tableRowComponent={
           this.rowRenderComponent(props => <TableRow {...props} rows={rows} />)
          }
        />
        <TableHeaderRow />
      </Grid>
    );
  }
}
@kvet kvet self-assigned this Nov 21, 2017
kvet added a commit that referenced this issue Nov 22, 2017
#486)

BREAKING CHANGES:
 
The Grid's `rootTemplate`, `headerPlaceholderTemplate`, and `footerPlaceholderTemplate` properties have been replaced with `rootComponent`, `headerPlaceholderComponent`, and `footerPlaceholderComponent`. This also means that they accept components instead of render functions. Find more details here: #496
 
The `headerTemplate`, `bodyTemplate`, and `footerTemplate` properties have been replaced with the `children` property in `rootTemplate`.
@aislanmaia
Copy link

However, you can work around this limitation using any state management library. For example, Redux.

Is there some example how to do this with Redux?

Any prediction to release date of this breaking changes?
However, thanks for your work ❤️

@kvet
Copy link
Contributor Author

kvet commented Nov 27, 2017

Hi,
 
These changes will be available soon. An example with Redux will be in the "Featured Redux Integration" demo once we complete work.

kvet added a commit that referenced this issue Nov 28, 2017
…leView (#485)

BREAKING CHANGES:

The TableView's `tableLayoutTemplate`, `tableCellTemplate`, `tableRowTemplate`, `tableNoDataCellTemplate`, `tableNoDataRowTemplate`, `tableStubCellTemplate`, and `tableStubHeaderCellComponent` properties have been replaced with `layoutComponent`, `getCellComponent`, `rowComponent`, `noDataCellComponent`, `noDataRowComponent`, `stubCellComponent` and `stubHeaderCellComponent`. This also means that they accept components instead of render functions. Find more details here: #496
kvet added a commit that referenced this issue Dec 4, 2017
…leSelection (#502)

BREAKING CHANGES:

The TableSelection plugin's `highlightSelected` property has been renamed to `highlightRow`. The `selectCellTemplate` and `selectAllCellTemplate` properties have been replaced with `cellComponent`, and `headerCellComponent` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to `headerCellComponent` have the same names as arguments passed to the `selectAllCellTemplate` function with the following exceptions:
 - The `onToggle` property is used instead of the `toggleAll` argument.
 - The `disabled` property is used instead of the `selectionAvailable` argument and it's value is inverted.

Properties passed to `cellComponent` have the same names as arguments passed to the `selectCellTemplate` function except for the `onToggle` property, which is used instead of the `changeSelected` argument.
kvet added a commit that referenced this issue Dec 4, 2017
…leRowDetail (#505)

BREAKING CHANGES:

The TableRowDetail plugin's `detailToggleCellWidth` property has been renamed to `toggleColumnWidth`. The `template`, `detailCellTemplate`, `detailRowTemplate`, and `detailToggleCellTemplate` properties have been replaced with  `contentComponent`, `cellComponent`, `rowComponent`, and `toggleCellComponent` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to `cellComponent` have the same names as arguments passed to the `detailCellTemplate` function except for the `children` property, which is used instead of the `template` argument.

Properties passed to `toggleCellComponent` have the same names as arguments passed to the `detailToggleCellTemplate` function except for the `onToggle` property, which is used instead of the `toggleExpanded` argument.
kvet added a commit that referenced this issue Dec 5, 2017
…leHeaderRow (#508)

BREAKING CHANGES:

The TableHeaderRow's `headerCellTemplate`, and `headerRowTemplate` properties have been replaced with `getCellComponent`, and `rowComponent`, which accept components instead of render functions. Find more details here: #496

Properties passed to `getCellComponent` have the same names as arguments passed to the `headerCellTemplate` function with the following exceptions: the `onSort`, `onGroup`, `onWidthChange` and `onDraftWidthChange` properties are used instead of the `changeSortingDirection`, `groupByColumn`, `changeColumnWidth` and `changeDraftColumnWidth` arguments respectively.
kvet added a commit that referenced this issue Dec 7, 2017
…bleFilterRow (#512)

BREAKING CHANGES:

The TableFilterRow plugin's `filterCellTemplate` and `filterRowTemplate` properties have been replaced with `getCellComponent`, and `rowComponent` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to `getCellComponent` have the same names as arguments passed to the `filterCellTemplate` function except for the `onFilter` property, which is used instead of the `setFilter` argument.
kvet added a commit that referenced this issue Dec 7, 2017
…leGroupRow (#511)

BREAKING CHANGES:

The TableGroupRow plugin's `groupIndentColumnWidth` property has been renamed to `indentColumnWidth`. The `groupCellTemplate`, `groupRowTemplate` and `groupIndentCellTemplate` properties have been replaced with `getCellComponent`, `rowComponent`, and `indentCellComponent` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to `getCellComponent` have the same names as arguments passed to the `groupCellTemplate` function with the following exceptions:
 - The `onToggle` property is used instead of the `toggleGroupExpanded` argument.
 - The `expanded` property is used instead of the `isExpanded` argument.
kvet pushed a commit that referenced this issue Dec 8, 2017
…aTypeProvider (#513)

BREAKING CHANGE

The DataTypeProvider plugin's `formatterTemplate` and `editorTemplate` properties have been replaced with `formatterComponent`, and `editorComponent` ones, which accept components instead of render functions. Find more details here: #496
kvet pushed a commit that referenced this issue Dec 8, 2017
…gDropContext (#524)

BREAKING CHANGES

The DragDropContext plugin's `containerTemplate ` and `columnTemplate ` properties have been replaced with `containerComponent ` and `columnComponent ` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to the `containerComponent` property have the same names as arguments passed to the `containerTemplate` function except for the `children` property which is used instead of the `columns` and `columnTemplate` arguments.
kvet pushed a commit that referenced this issue Dec 8, 2017
…leColumnVisibility (#530)

BREAKING CHANGE

The `emptyMessageTemplate` property of the `TableColumnVisibility` plugin has been replaced with `emptyMessageComponent`. The new property accepts components instead of render functions. Find more details here: #496
kvet pushed a commit that referenced this issue Dec 8, 2017
…ingPanel (#527)

BREAKING CHANGE

The PagingPanel `pagerTemplate` property has been replaced with `containerComponent` one, which accepts a component instead of a render function. Find more details here: #496
kvet pushed a commit that referenced this issue Dec 8, 2017
…leColumnReordering (#528)

BREAKING CHANGES

The TableColumnReordering plugin's `tableContainerTemplate`, `reorderingRowTemplate` and `reorderingCellTemplate` properties have been replaced with `tableContainerComponent` and `rowComponent`, `cellComponent` ones which accept components instead of render functions. Find more details here: #496
kvet added a commit that referenced this issue Dec 8, 2017
…bleEditColumn (#522)

BREAKING CHANGES:

The TableEditColumn plugin's `commandTemplate`, `cellTemplate` and `headingCellTemplate` properties have been replaced with `getCommandComponent`, `cellComponent`, and `headerCellComponent` ones, which accept components instead of render functions. Find more details here: #496

Properties passed to a component returned from `getCommandComponent` have the same names as arguments passed to the `commandTemplate` function with the following exception. The `onExecute` property is used instead of the `executeCommand` argument.

All properties passed to the `cellTemplate` except `row` have been replaced by the `children` property providing configured commands.

All properties passed to the `headingCellTemplate` have been replaced by the `children` property providing configured commands.
kvet added a commit that referenced this issue Dec 11, 2017
…upingPanel (#540)

BREAKING CHANGES:

The GroupingPanel plugin's `groupPanelTemplate`, and `groupPanelItemTemplate` properties have been replaced with  `containerComponent`, and `itemComponent` ones, which accept components instead of render functions. Find more details here: #496 

`containerComponent` takes on the `children` property instead of all arguments passed to the `groupPanelTemplate` function.

The `onSort`, and `onGroup` properties passed to `itemComponent` are used instead of the `changeSortingDirection`, and `groupByColumn` arguments passed to the `groupPanelItemTemplate` function. The `item`, and `draft` properties are no longer available, use the `item` property instead.
kvet pushed a commit that referenced this issue Dec 11, 2017
…umnChooser (#514)

BREAKING CHANGE

The ColumnChooser plugin's `containerTemplate` and `itemTemplate` properties have been replaced with `containerComponent` and `itemComponent` ones, which accept components instead of render functions. Find more details here: #496
kvet added a commit that referenced this issue Dec 11, 2017
…bleEditRow (#518)

BREAKING CHANGES:

The TableEditRow plugin's `editCellTemplate`, and `editRowTemplate` properties have been replaced with `getCellComponent`, and `rowComponent` ones, which accept components instead of render functions. Find more details here: #496
@kvet
Copy link
Contributor Author

kvet commented Dec 14, 2017

We've released the 1.0.0-beta.2 version containing described changes.

@SergeyAlexeev
Copy link
Contributor

We published a function that contains the possible solution described in the first post of this thread.

@elmeerr
Copy link

elmeerr commented Feb 28, 2019

@SergeyAlexeev do you mind explaining how to use connectProps?

@DmitryBogomolov
Copy link
Contributor

Usage of connectProps is shown in the Access the Parent Component’s State Correctly section.
In addition, refer to this issue.

@elmeerr
Copy link

elmeerr commented Feb 28, 2019

@DmitryBogomolov Thanks!! that's what I needed. But I still have an issue (I hope you don't mind me asking here). I've created a custom TypeProvider for dropdown...everytime the select changes it loses focus (even with the approach you mentioned)

const SelectComponent = props => <SelectCell {...props} />

const SelectTypeProvider = props => {
    const SelectEditor = connectProps(SelectComponent, () => ({ options: props.options }))
    return <DataTypeProvider editorComponent={SelectEditor} for={props.for} />
}

I'm wondering if I use DataTypeProvider inside the actual component instead of using a Custom DataTypeProvider will solve the issue.

Do you know what could be the reason for losing focus?

Thanks again

@DmitryBogomolov
Copy link
Contributor

connectProps should not be called inside render. In your sample every time SelectTypeProvider is rendered new SelectEditor component is created. Create SelectEditor in constructor.
Here is a sample:

class SelectTypeProvider extends React.PureComponent {
    constructor(props) {
       ...
       this.editor = connectProps(SelectComponent, () => ({ options: this.props.options }));
    }
    
    componentDidUpdate() {
       ...
       this.editor.update();
    }
    
    render() {
        return <DataTypeProvider editorComponent={this.editor} for={this.props.for} />
    }
}

@elmeerr
Copy link

elmeerr commented Feb 28, 2019

@DmitryBogomolov as I have imagined. Thanks!!!

@durre
Copy link

durre commented Mar 6, 2019

Is there an elegant way of accomplishing the same thing using functional components & hooks? I've experimented a bit with React.memo & useRef, but no luck so far.

@elmeerr
Copy link

elmeerr commented Mar 6, 2019

@durre I didn't try but following the concept it will work if you put it inside of useEffect as it's the same thing as componentDidMount

@dxrobot dxrobot added the Grid The DevExtreme Reactive Grid component label May 2, 2019
@lock
Copy link

lock bot commented Jun 15, 2019

This thread has been automatically locked since it is closed and there has not been any recent activity. Please open a new issue for related bugs or feature requests.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement Grid The DevExtreme Reactive Grid component
Projects
None yet
Development

No branches or pull requests

7 participants