reactjsreact-componentreact-state

Why is my "inherited" react component losing state?


I created a react component using composition. It is two generations removed from the "parent" component. My component adds default sort indicators to a table column header.

The base component is TableHeaderColumn from react-bootstrap-table.

The first generation component composed from that looks like the following:

const columnFilterTypes = {
  DATE_FILTER: 'DateFilter',
  ARRAY_FILTER: 'ArrayFilter',
};

export class FilterTableHeaderColumn extends React.Component {
  static propTypes = {
    filter: shape({
      label: string,
      type: oneOf([columnFilterTypes.DATE_FILTER, columnFilterTypes.ARRAY_FILTER]),
    }),
  }
  static defaultProps = {
    dataSort: true,
  }
  render() {
    return (
      <TableHeaderColumn
        {...{ ...this.props, filter: undefined }}
        ref={(el) => { this.column = el; }} />
    );
  }
}

My component is composed from FilterTableHeaderColumn and looks like this:

class SortTableHeaderColumn extends React.Component {
    renderCaret = (direction, fieldName) => {
        if (this.props.sortIndicator) {
            if (direction === 'asc') {
                return "up";
            }
            if (direction === 'desc') {
                return "down";
            }
            return "up/down";
        }
    }

    render() {
        return (<FilterTableHeaderColumn  {...{ ...this.props  }} caretRender={this.renderCaret} />);
    }
}

The intent of my component is to behave exactly like the FilterTableHeaderColumn with an additional default caretRender property set.

When I use this component as a column header, I click the column header to sort the table by that column and the default ("up/down") sort indicators are always shown and the column only sorts in one direction (clicking again does not reverse the sort direction as it should). It seems like the state is getting lost somehow.


Solution

  • This looks to me like it's because react-bootstrap-table uses a very old-fashioned and outmoded way of passing its state down to the child components it owns. It's using the React.children API to loop the header components, and extract info from them. Actually, it's so bad, I'd consider using a more sane library that's based on context. This library looks very old and unsupported and uses patterns which are long gone.

    Since you have now introduced a wrapped version of the base component, the expectations of the library are broken. It seems to start checking component types around here.

    However, if you must push on, you can achieve what you need without the wrapper by moving renderCaret into FilterTableHeaderColumn and passing that straight down to the base component instead.

    This is ridiculous but you might also try naming your component the to be the same one as in the library, and alias the import for the base one.

    But yeah, libraries that do this really suck.