Thursday, January 24, 2019

React: How to add nested table in Semantic UI React Table using React Fragment.


Context:
class Table extends React.Component {
  render() {
    return (
      <table>
        <tr>
          <Columns />
        </tr>
      </table>
    );
  }
}
<Columns /> would need to return multiple <td> elements in order for the rendered HTML to be valid. If a parent div was used inside the render() of <Columns />, then the resulting HTML will be invalid.
class Columns extends React.Component {
  render() {
    return (
      <div>
        <td>Hello</td>
        <td>World</td>
      </div>
    );
  }
}
In our case we want to insert a different row when the user want to expand the select row.
In order to solve this issue, we have to use React fragments.
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}
which results in a correct <Table /> output of:
<table>
  <tr>
    <td>Hello</td>
    <td>World</td>
  </tr>
</table>

import * as React from "react";
import CSSModules from "react-css-modules";
import { connect } from "react-redux";
import ReactTable from "react-table";
import {
  Breadcrumb,
  Button,
  Dimmer,
  Icon,
  Input,
  Loader,
  Table
} from "semantic-ui-react";
import { repsActions } from "../_actions";
import AdminPageTable from "../_components/AdminPageTable";
import { GreenLed, RedLed } from "../_components/Leds";
import { WrappedButton } from "../_components/WrappedButton";
import { history } from "../_helpers";

const styles = require("./AdminPage.less");
const options = {
  allowMultiple: true
};

interface SelectedHealthCheck {
  name: string;
  index: number;
  display: boolean;
}

interface AdminPageState {
  displayHealthCheck: SelectedHealthCheck;
}

interface AdminPageProps {
  displayHealthCheck: boolean;
  getAllReps: () => any;
  reps: any;
}

@CSSModules(styles, options)
class ConnectedAdminPage extends React.Component<
  AdminPageProps,
  AdminPageState
> {
  constructor(props) {
    super(props);
    this.state = {
      displayHealthCheck: { name: null, index: 0, display: false }
    };
  }

  public componentDidMount() {
    this.props.getAllReps();
  }

  public handleNav = e => {
    e.preventDefault();
    history.push("/");
  };

  public handleCellClick = (selected, idx) => {
    if (this.state.displayHealthCheck.name === selected) {
      this.setState(prevState => ({
        displayHealthCheck: {
          name: selected,
          index: idx,
          display: !prevState.displayHealthCheck.display
        }
      }));
    } else {
      this.setState({
        displayHealthCheck: { name: selected, index: idx, display: true }
      });
    }
  };

  public render() {
    const {
      reps: { reps }
    } = this.props;
    const { displayHealthCheck } = this.state;

    if (!reps) {
      return null;
    }
    if (reps.loading) {
      return (
        <Dimmer active>
          <Loader>Loading</Loader>
        </Dimmer>
      );
    }
    if (reps.error) {
      return <div>Error! {reps.error.message}</div>;
    }
    return (
      <div styleName="page-container">
        <div styleName="header-row ">
          <div styleName="item">
            <div styleName="title">Admin</div>
            <Breadcrumb styleName="breadcrumb-container">
              <Breadcrumb.Section onClick={this.handleNav}>
                Dashboard
              </Breadcrumb.Section>
              <Breadcrumb.Divider />
              <Breadcrumb.Section>Admin</Breadcrumb.Section>
            </Breadcrumb>
          </div>
          <div styleName="btn-container">
            <Button
              id="override-button"
              styleName="button-override"
              onClick={this.props.getAllReps}
              size="small"
            >
              <Icon name="refresh" />
              Refresh
            </Button>
          </div>
        </div>
        <div styleName="admin-table-container">
          <Table basic="very">
            <Table.Header>
              <Table.Row>
                <Table.HeaderCell />
                <Table.HeaderCell width={1}>Name</Table.HeaderCell>
                <Table.HeaderCell width={1}>Display Name</Table.HeaderCell>
                <Table.HeaderCell width={1}>Provisioner State</Table.HeaderCell>
                <Table.HeaderCell width={1}>Version</Table.HeaderCell>
                <Table.HeaderCell width={1}>Status</Table.HeaderCell>
                <Table.HeaderCell />
              </Table.Row>
            </Table.Header>

            <Table.Body>
              {reps.map((rep, index) => {
                return (
                  <AdminPageTable
                    rep={rep}
                    handleCellClick={this.handleCellClick}
                    displayHealthCheck={displayHealthCheck}
                    index={index}
                  />
                );
              })}
            </Table.Body>
          </Table>
        </div>
      </div>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return {
    getAllReps: () => {
      dispatch(repsActions.getAllwithHealthStatus());
    }
  };
}

const mapStateToProps = state => {
  const { reps } = state;
  return {
    reps
  };
};

const AdminPage = connect(
  mapStateToProps,
  mapDispatchToProps
)(ConnectedAdminPage);
export default AdminPage;


The goal is to define 2 table rows fragments. Both rows will be loaded in the previous html table. The second row has a conditional display.

import * as React from "react";
import { confirmAlert } from "react-confirm-alert";
import "react-confirm-alert/src/react-confirm-alert.css";
import CSSModules from "react-css-modules";
import { connect } from "react-redux";
import { Button, Dropdown, Icon, Loader, Menu, Table } from "semantic-ui-react";
import { repsActions } from "../../_actions";
import { adminConstants, servicesConstants } from "../../_constants";
import { history } from "../../_helpers";
import HealthCheckPage from "../../HealthCheckPage";
import AdminTableMenu from "../AdminTableMenu";
import { GreenLed, RedLed, YellowLed } from "../Leds";

const styles = require("./AdminPageTable.less");
const options = {
  allowMultiple: true
};

interface SelectedHealthCheck {
  name: string;
  index: number;
  display: boolean;
}
interface Rep {
  apiLink: string;
  name: string;
  description: string;
  displayName: string;
  healthStatus: string;
  locations: any;
  v4name: string;
  status: string;
  disabled: boolean;
  version: string;
}

interface AdminPageTableProps {
  rep: Rep;
  displayHealthCheck: SelectedHealthCheck;
  handleCellClick: (name: string, index: number) => any;
  index: number;
  getMicroServiceHealth: (name: string) => any;
  repsConfig: any;
}

@CSSModules(styles, options)
export class AdminPageTable extends React.Component<AdminPageTableProps, any> {
  public render() {
    const {
      rep,
      displayHealthCheck,
      handleCellClick,
      index,
      repsConfig,
      repsConfig: { currentHealth = { status: adminConstants.DOWN } }
    } = this.props;

    return (
      <React.Fragment>
        <Table.Row textAlign="left">
          <Table.Cell
            width={1}
            collapsing
            selectable
            onClick={() => handleCellClick(rep.name, index)}
          >
            <Icon link name="caret square down outline" size="large" />
          </Table.Cell>
          <Table.Cell
            width={3}
            selectable
            onClick={() => handleCellClick(rep.name, index)}
          >
            {rep.name}
          </Table.Cell>
          <Table.Cell width={3}>{rep.v4name}</Table.Cell>
          <Table.Cell width={3}>
            {rep.disabled ? <RedLed /> : <GreenLed />}
          </Table.Cell>
          <Table.Cell width={3}>{rep.version}</Table.Cell>
          <Table.Cell>
            {repsConfig.loading ? (
              <Loader active inline />
            ) : rep.healthStatus === adminConstants.UP ? (
              <GreenLed />
            ) : rep.healthStatus === adminConstants.DOWN ? (
              <RedLed />
            ) : rep.healthStatus === adminConstants.DEGRADED ? (
              <YellowLed />
            ) : (
              <RedLed />
            )}
          </Table.Cell>
          <Table.Cell width={1}>
            <AdminTableMenu rowprops={rep} />
          </Table.Cell>
        </Table.Row>

        {displayHealthCheck.display && displayHealthCheck.index === index ? (
          <Table.Row textAlign="left">
            <Table.Cell width={1} />
            <Table.Cell colSpan="5">
              <HealthCheckPage microservice={displayHealthCheck.name} />
            </Table.Cell>
          </Table.Row>
        ) : null}
      </React.Fragment>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return {
    getMicroServiceHealth: name => {
      dispatch(repsActions.getMicroServiceHealth(name));
    }
  };
}

const mapStateToProps = state => {
  return {
    repsConfig: state.repsConfig
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(AdminPageTable);



2 comments:

  1. Do you have the code for the HealthCheck page? This stuff is really useful.

    ReplyDelete