Table

A table displays rows of data.

When To Use#

  • To display a collection of structured data.

  • To sort, search, paginate, filter data.

How To Use#

Specify dataSource of Table as an array of data.

const dataSource = [{
  key: '1',
  name: 'Mike',
  age: 32,
  address: '10 Downing Street'
}, {
  key: '2',
  name: 'John',
  age: 42,
  address: '10 Downing Street'
}];

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
  key: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
  key: 'address',
}];

<Table dataSource={dataSource} columns={columns} />

Examples

Name
Age
Address
Tags
Action
First Name
Last Name
JohnBrown32New York No. 1 Lake Park
nice
developer
Invite Brown
Delete
JimGreen42London No. 1 Lake Park
loser
Invite Green
Delete
JoeBlack32Sidney No. 1 Lake Park
cool
teacher
Invite Black
Delete

Using JSX style API (introduced in 2.5.0)

Since this is just a syntax sugar for the prop columns, you can't compose Column and ColumnGroup with other Components.

expand codeexpand code
import { Table, Divider, Tag } from 'antd';

const { Column, ColumnGroup } = Table;

const data = [{
  key: '1',
  firstName: 'John',
  lastName: 'Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
  tags: ['nice', 'developer'],
}, {
  key: '2',
  firstName: 'Jim',
  lastName: 'Green',
  age: 42,
  address: 'London No. 1 Lake Park',
  tags: ['loser'],
}, {
  key: '3',
  firstName: 'Joe',
  lastName: 'Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
  tags: ['cool', 'teacher'],
}];

ReactDOM.render(
  <Table dataSource={data}>
    <ColumnGroup title="Name">
      <Column
        title="First Name"
        dataIndex="firstName"
        key="firstName"
      />
      <Column
        title="Last Name"
        dataIndex="lastName"
        key="lastName"
      />
    </ColumnGroup>
    <Column
      title="Age"
      dataIndex="age"
      key="age"
    />
    <Column
      title="Address"
      dataIndex="address"
      key="address"
    />
    <Column
      title="Tags"
      dataIndex="tags"
      key="tags"
      render={tags => (
        <span>
          {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
        </span>
      )}
    />
    <Column
      title="Action"
      key="action"
      render={(text, record) => (
        <span>
          <a href="javascript:;">Invite {record.lastName}</a>
          <Divider type="vertical" />
          <a href="javascript:;">Delete</a>
        </span>
      )}
    />
  </Table>,
  mountNode
);
Name
Age
Address
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park
Disabled User99Sidney No. 1 Lake Park

Rows can be selectable by making first column as a selectable column.

selection happens when clicking checkbox defaultly. You can see https://codesandbox.io/s/000vqw38rl if you need row-click selection behavior.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  render: text => <a href="javascript:;">{text}</a>,
}, {
  title: 'Age',
  dataIndex: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
}];
const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}, {
  key: '4',
  name: 'Disabled User',
  age: 99,
  address: 'Sidney No. 1 Lake Park',
}];

// rowSelection object indicates the need for row selection
const rowSelection = {
  onChange: (selectedRowKeys, selectedRows) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  getCheckboxProps: record => ({
    disabled: record.name === 'Disabled User', // Column configuration not to be checked
    name: record.name,
  }),
};

ReactDOM.render(
  <Table rowSelection={rowSelection} columns={columns} dataSource={data} />,
  mountNode
);
Name
Age
Address
Edward King 032London, Park Lane no. 0
Edward King 132London, Park Lane no. 1
Edward King 232London, Park Lane no. 2
Edward King 332London, Park Lane no. 3
Edward King 432London, Park Lane no. 4
Edward King 532London, Park Lane no. 5
Edward King 632London, Park Lane no. 6
Edward King 732London, Park Lane no. 7
Edward King 832London, Park Lane no. 8
Edward King 932London, Park Lane no. 9

To perform operations and clear selections after selecting some rows, use rowSelection.selectedRowKeys to control selected rows.

expand codeexpand code
import { Table, Button } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
}];

const data = [];
for (let i = 0; i < 46; i++) {
  data.push({
    key: i,
    name: `Edward King ${i}`,
    age: 32,
    address: `London, Park Lane no. ${i}`,
  });
}

class App extends React.Component {
  state = {
    selectedRowKeys: [], // Check here to configure the default column
    loading: false,
  };

  start = () => {
    this.setState({ loading: true });
    // ajax request after empty completing
    setTimeout(() => {
      this.setState({
        selectedRowKeys: [],
        loading: false,
      });
    }, 1000);
  }

  onSelectChange = (selectedRowKeys) => {
    console.log('selectedRowKeys changed: ', selectedRowKeys);
    this.setState({ selectedRowKeys });
  }

  render() {
    const { loading, selectedRowKeys } = this.state;
    const rowSelection = {
      selectedRowKeys,
      onChange: this.onSelectChange,
    };
    const hasSelected = selectedRowKeys.length > 0;
    return (
      <div>
        <div style={{ marginBottom: 16 }}>
          <Button
            type="primary"
            onClick={this.start}
            disabled={!hasSelected}
            loading={loading}
          >
            Reload
          </Button>
          <span style={{ marginLeft: 8 }}>
            {hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
          </span>
        </div>
        <Table rowSelection={rowSelection} columns={columns} dataSource={data} />
      </div>
    );
  }
}

ReactDOM.render(<App />, mountNode);
Name
Age
Address
Edward King 032London, Park Lane no. 0
Edward King 132London, Park Lane no. 1
Edward King 232London, Park Lane no. 2
Edward King 332London, Park Lane no. 3
Edward King 432London, Park Lane no. 4
Edward King 532London, Park Lane no. 5
Edward King 632London, Park Lane no. 6
Edward King 732London, Park Lane no. 7
Edward King 832London, Park Lane no. 8
Edward King 932London, Park Lane no. 9

Use rowSelection.selections custom selections, default no select dropdown, show default selections via setting to true.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
}];

const data = [];
for (let i = 0; i < 46; i++) {
  data.push({
    key: i,
    name: `Edward King ${i}`,
    age: 32,
    address: `London, Park Lane no. ${i}`,
  });
}

class App extends React.Component {
  state = {
    selectedRowKeys: [], // Check here to configure the default column
  };

  onSelectChange = (selectedRowKeys) => {
    console.log('selectedRowKeys changed: ', selectedRowKeys);
    this.setState({ selectedRowKeys });
  }

  render() {
    const { selectedRowKeys } = this.state;
    const rowSelection = {
      selectedRowKeys,
      onChange: this.onSelectChange,
      hideDefaultSelections: true,
      selections: [{
        key: 'all-data',
        text: 'Select All Data',
        onSelect: () => {
          this.setState({
            selectedRowKeys: [...Array(46).keys()], // 0...45
          });
        },
      }, {
        key: 'odd',
        text: 'Select Odd Row',
        onSelect: (changableRowKeys) => {
          let newSelectedRowKeys = [];
          newSelectedRowKeys = changableRowKeys.filter((key, index) => {
            if (index % 2 !== 0) {
              return false;
            }
            return true;
          });
          this.setState({ selectedRowKeys: newSelectedRowKeys });
        },
      }, {
        key: 'even',
        text: 'Select Even Row',
        onSelect: (changableRowKeys) => {
          let newSelectedRowKeys = [];
          newSelectedRowKeys = changableRowKeys.filter((key, index) => {
            if (index % 2 !== 0) {
              return true;
            }
            return false;
          });
          this.setState({ selectedRowKeys: newSelectedRowKeys });
        },
      }],
      onSelection: this.onSelection,
    };
    return (
      <Table rowSelection={rowSelection} columns={columns} dataSource={data} />
    );
  }
}

ReactDOM.render(<App />, mountNode);
Name
Age
Address
Jim Green42London No. 1 Lake Park
John Brown32New York No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park
Jim Red32London No. 2 Lake Park

Use filters to generate filter menu in columns, onFilter to determine filtered result, and filterMultiple to indicate whether it's multiple or single selection.

Use sorter to make a column sortable. sorter can be a function of the type function(a, b) { ... } for sorting data locally.

sortDirections: ['ascend' | 'descend'] defines available sort methods for each columns, effective for all columns when set on table props.

Uses defaultSortOrder to make a column sorted by default.

If a sortOrder or defaultSortOrder is specified with the value ascend or descend, you can access this value from within the function passed to the sorter as explained above. Such a function can take the form: function(a, b, sortOrder) { ... }.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  filters: [{
    text: 'Joe',
    value: 'Joe',
  }, {
    text: 'Jim',
    value: 'Jim',
  }, {
    text: 'Submenu',
    value: 'Submenu',
    children: [{
      text: 'Green',
      value: 'Green',
    }, {
      text: 'Black',
      value: 'Black',
    }],
  }],
  // specify the condition of filtering result
  // here is that finding the name started with `value`
  onFilter: (value, record) => record.name.indexOf(value) === 0,
  sorter: (a, b) => a.name.length - b.name.length,
  sortDirections: ['descend'],
}, {
  title: 'Age',
  dataIndex: 'age',
  defaultSortOrder: 'descend',
  sorter: (a, b) => a.age - b.age,
}, {
  title: 'Address',
  dataIndex: 'address',
  filters: [{
    text: 'London',
    value: 'London',
  }, {
    text: 'New York',
    value: 'New York',
  }],
  filterMultiple: false,
  onFilter: (value, record) => record.address.indexOf(value) === 0,
  sorter: (a, b) => a.address.length - b.address.length,
  sortDirections: ['descend', 'ascend'],
}];

const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}, {
  key: '4',
  name: 'Jim Red',
  age: 32,
  address: 'London No. 2 Lake Park',
}];

function onChange(pagination, filters, sorter) {
  console.log('params', pagination, filters, sorter);
}

ReactDOM.render(
  <Table columns={columns} dataSource={data} onChange={onChange} />,
  mountNode
);
Name
Age
Address
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park
Jim Red32London No. 2 Lake Park

Control filters and sorters by filteredValue and sortOrder.

  1. Defining filteredValue or sortOrder means that it is in the controlled mode.

  2. Make sure sortOrder is assigned for only one column.

  3. column.key is required.

expand codeexpand code
import { Table, Button } from 'antd';

const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}, {
  key: '4',
  name: 'Jim Red',
  age: 32,
  address: 'London No. 2 Lake Park',
}];

class App extends React.Component {
  state = {
    filteredInfo: null,
    sortedInfo: null,
  };

  handleChange = (pagination, filters, sorter) => {
    console.log('Various parameters', pagination, filters, sorter);
    this.setState({
      filteredInfo: filters,
      sortedInfo: sorter,
    });
  }

  clearFilters = () => {
    this.setState({ filteredInfo: null });
  }

  clearAll = () => {
    this.setState({
      filteredInfo: null,
      sortedInfo: null,
    });
  }

  setAgeSort = () => {
    this.setState({
      sortedInfo: {
        order: 'descend',
        columnKey: 'age',
      },
    });
  }

  render() {
    let { sortedInfo, filteredInfo } = this.state;
    sortedInfo = sortedInfo || {};
    filteredInfo = filteredInfo || {};
    const columns = [{
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
      filters: [
        { text: 'Joe', value: 'Joe' },
        { text: 'Jim', value: 'Jim' },
      ],
      filteredValue: filteredInfo.name || null,
      onFilter: (value, record) => record.name.includes(value),
      sorter: (a, b) => a.name.length - b.name.length,
      sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
    }, {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
      sorter: (a, b) => a.age - b.age,
      sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
    }, {
      title: 'Address',
      dataIndex: 'address',
      key: 'address',
      filters: [
        { text: 'London', value: 'London' },
        { text: 'New York', value: 'New York' },
      ],
      filteredValue: filteredInfo.address || null,
      onFilter: (value, record) => record.address.includes(value),
      sorter: (a, b) => a.address.length - b.address.length,
      sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
    }];
    return (
      <div>
        <div className="table-operations">
          <Button onClick={this.setAgeSort}>Sort age</Button>
          <Button onClick={this.clearFilters}>Clear filters</Button>
          <Button onClick={this.clearAll}>Clear filters and sorters</Button>
        </div>
        <Table columns={columns} dataSource={data} onChange={this.handleChange} />
      </div>
    );
  }
}

ReactDOM.render(<App />, mountNode);
.table-operations {
  margin-bottom: 16px;
}

.table-operations > button {
  margin-right: 8px;
}
Name
Age
Address
John Brown32New York No. 1 Lake Park
Joe Black42London No. 1 Lake Park
Jim Green32Sidney No. 1 Lake Park
Jim Red32London No. 2 Lake Park

Implement a customized column search example via filterDropdown.

expand codeexpand code
import {
  Table, Input, Button, Icon,
} from 'antd';
import Highlighter from 'react-highlight-words';

const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Joe Black',
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Jim Green',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}, {
  key: '4',
  name: 'Jim Red',
  age: 32,
  address: 'London No. 2 Lake Park',
}];

class App extends React.Component {
  state = {
    searchText: '',
  };

  getColumnSearchProps = (dataIndex) => ({
    filterDropdown: ({
      setSelectedKeys, selectedKeys, confirm, clearFilters,
    }) => (
      <div style={{ padding: 8 }}>
        <Input
          ref={node => { this.searchInput = node; }}
          placeholder={`Search ${dataIndex}`}
          value={selectedKeys[0]}
          onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
          onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
          style={{ width: 188, marginBottom: 8, display: 'block' }}
        />
        <Button
          type="primary"
          onClick={() => this.handleSearch(selectedKeys, confirm)}
          icon="search"
          size="small"
          style={{ width: 90, marginRight: 8 }}
        >
          Search
        </Button>
        <Button
          onClick={() => this.handleReset(clearFilters)}
          size="small"
          style={{ width: 90 }}
        >
          Reset
        </Button>
      </div>
    ),
    filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
    onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
    onFilterDropdownVisibleChange: (visible) => {
      if (visible) {
        setTimeout(() => this.searchInput.select());
      }
    },
    render: (text) => (
      <Highlighter
        highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
        searchWords={[this.state.searchText]}
        autoEscape
        textToHighlight={text.toString()}
      />
    ),
  })

  handleSearch = (selectedKeys, confirm) => {
    confirm();
    this.setState({ searchText: selectedKeys[0] });
  }

  handleReset = (clearFilters) => {
    clearFilters();
    this.setState({ searchText: '' });
  }

  render() {
    const columns = [{
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
      width: '30%',
      ...this.getColumnSearchProps('name'),
    }, {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
      width: '20%',
      ...this.getColumnSearchProps('age'),
    }, {
      title: 'Address',
      dataIndex: 'address',
      key: 'address',
      ...this.getColumnSearchProps('address'),
    }];
    return <Table columns={columns} dataSource={data} />;
  }
}

ReactDOM.render(<App />, mountNode);
Name
Gender
Email
No Data

No Data

This example shows how to fetch and present data from a remote server, and how to implement filtering and sorting in server side by sending related parameters to server.

Note, this example use Mock API that you can look up in Network Console.

expand codeexpand code
import { Table } from 'antd';
import reqwest from 'reqwest';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  sorter: true,
  render: name => `${name.first} ${name.last}`,
  width: '20%',
}, {
  title: 'Gender',
  dataIndex: 'gender',
  filters: [
    { text: 'Male', value: 'male' },
    { text: 'Female', value: 'female' },
  ],
  width: '20%',
}, {
  title: 'Email',
  dataIndex: 'email',
}];

class App extends React.Component {
  state = {
    data: [],
    pagination: {},
    loading: false,
  };

  componentDidMount() {
    this.fetch();
  }

  handleTableChange = (pagination, filters, sorter) => {
    const pager = { ...this.state.pagination };
    pager.current = pagination.current;
    this.setState({
      pagination: pager,
    });
    this.fetch({
      results: pagination.pageSize,
      page: pagination.current,
      sortField: sorter.field,
      sortOrder: sorter.order,
      ...filters,
    });
  }

  fetch = (params = {}) => {
    console.log('params:', params);
    this.setState({ loading: true });
    reqwest({
      url: 'https://randomuser.me/api',
      method: 'get',
      data: {
        results: 10,
        ...params,
      },
      type: 'json',
    }).then((data) => {
      const pagination = { ...this.state.pagination };
      // Read total count from server
      // pagination.total = data.totalCount;
      pagination.total = 200;
      this.setState({
        loading: false,
        data: data.results,
        pagination,
      });
    });
  }

  render() {
    return (
      <Table
        columns={columns}
        rowKey={record => record.login.uuid}
        dataSource={this.state.data}
        pagination={this.state.pagination}
        loading={this.state.loading}
        onChange={this.handleTableChange}
      />
    );
  }
}

ReactDOM.render(<App />, mountNode);

Middle size table

Name
Age
Address
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park

Small size table

Name
Age
Address
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park

Two compacted table size: middle and small, small size is used in Modal only.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
}];
const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}];

ReactDOM.render(
  <div>
    <h4>Middle size table</h4>
    <Table columns={columns} dataSource={data} size="middle" />
    <h4>Small size table</h4>
    <Table columns={columns} dataSource={data} size="small" />
  </div>,
  mountNode
);
Header
Name
Cash Assets
Address
John Brown¥300,000.00New York No. 1 Lake Park
Jim Green¥1,256,000.00London No. 1 Lake Park
Joe Black¥120,000.00Sidney No. 1 Lake Park

Add border, title and footer for table.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  render: text => <a href="javascript:;">{text}</a>,
}, {
  title: 'Cash Assets',
  className: 'column-money',
  dataIndex: 'money',
}, {
  title: 'Address',
  dataIndex: 'address',
}];

const data = [{
  key: '1',
  name: 'John Brown',
  money: '¥300,000.00',
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  money: '¥1,256,000.00',
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  money: '¥120,000.00',
  address: 'Sidney No. 1 Lake Park',
}];

ReactDOM.render(
  <Table
    columns={columns}
    dataSource={data}
    bordered
    title={() => 'Header'}
    footer={() => 'Footer'}
  />,
  mountNode
);
th.column-money,
td.column-money {
  text-align: right !important;
}
Name
Age
Address
Action
John Brown32New York No. 1 Lake ParkDelete
Jim Green42London No. 1 Lake ParkDelete
Joe Black32Sidney No. 1 Lake ParkDelete

When there's too much information to show and the table can't display all at once.

expand codeexpand code
import { Table } from 'antd';

const columns = [
  { title: 'Name', dataIndex: 'name', key: 'name' },
  { title: 'Age', dataIndex: 'age', key: 'age' },
  { title: 'Address', dataIndex: 'address', key: 'address' },
  {
    title: 'Action', dataIndex: '', key: 'x', render: () => <a href="javascript:;">Delete</a>,
  },
];

const data = [
  {
    key: 1, name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
  },
  {
    key: 2, name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
  },
  {
    key: 3, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
  },
];

ReactDOM.render(
  <Table
    columns={columns}
    expandedRowRender={record => <p style={{ margin: 0 }}>{record.description}</p>}
    dataSource={data}
  />,
  mountNode
);
Name
Age
Home phone
Address
John Brown320571-2209890918889898989New York No. 1 Lake Park
Jim Green420571-2209833318889898888London No. 1 Lake Park
Joe Black320575-2209890918900010002Sidney No. 1 Lake Park
Jim Red1818900010002London No. 2 Lake Park
Jake White

Table column title supports colSpan that set in column.

Table cell supports colSpan and rowSpan that set in render return object. When each of them is set to 0, the cell will not be rendered.

expand codeexpand code
import { Table } from 'antd';

// In the fifth row, other columns are merged into first column
// by setting it's colSpan to be 0
const renderContent = (value, row, index) => {
  const obj = {
    children: value,
    props: {},
  };
  if (index === 4) {
    obj.props.colSpan = 0;
  }
  return obj;
};

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  render: (text, row, index) => {
    if (index < 4) {
      return <a href="javascript:;">{text}</a>;
    }
    return {
      children: <a href="javascript:;">{text}</a>,
      props: {
        colSpan: 5,
      },
    };
  },
}, {
  title: 'Age',
  dataIndex: 'age',
  render: renderContent,
}, {
  title: 'Home phone',
  colSpan: 2,
  dataIndex: 'tel',
  render: (value, row, index) => {
    const obj = {
      children: value,
      props: {},
    };
    if (index === 2) {
      obj.props.rowSpan = 2;
    }
    // These two are merged into above cell
    if (index === 3) {
      obj.props.rowSpan = 0;
    }
    if (index === 4) {
      obj.props.colSpan = 0;
    }
    return obj;
  },
}, {
  title: 'Phone',
  colSpan: 0,
  dataIndex: 'phone',
  render: renderContent,
}, {
  title: 'Address',
  dataIndex: 'address',
  render: renderContent,
}];

const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  tel: '0571-22098909',
  phone: 18889898989,
  address: 'New York No. 1 Lake Park',
}, {
  key: '2',
  name: 'Jim Green',
  tel: '0571-22098333',
  phone: 18889898888,
  age: 42,
  address: 'London No. 1 Lake Park',
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  tel: '0575-22098909',
  phone: 18900010002,
  address: 'Sidney No. 1 Lake Park',
}, {
  key: '4',
  name: 'Jim Red',
  age: 18,
  tel: '0575-22098909',
  phone: 18900010002,
  address: 'London No. 2 Lake Park',
}, {
  key: '5',
  name: 'Jake White',
  age: 18,
  tel: '0575-22098909',
  phone: 18900010002,
  address: 'Dublin No. 2 Lake Park',
}];

ReactDOM.render(<Table columns={columns} dataSource={data} bordered />,
  mountNode);
Name
Age
Address
John Brown sr.60New York No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park

Display tree structure data in Table when there is field key children in dataSource, try to customize childrenColumnName property to avoid tree table structure.

You can control the indent width by setting indentSize.

Note, no support for recursive selection of tree structure data table yet.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
  key: 'age',
  width: '12%',
}, {
  title: 'Address',
  dataIndex: 'address',
  width: '30%',
  key: 'address',
}];

const data = [{
  key: 1,
  name: 'John Brown sr.',
  age: 60,
  address: 'New York No. 1 Lake Park',
  children: [{
    key: 11,
    name: 'John Brown',
    age: 42,
    address: 'New York No. 2 Lake Park',
  }, {
    key: 12,
    name: 'John Brown jr.',
    age: 30,
    address: 'New York No. 3 Lake Park',
    children: [{
      key: 121,
      name: 'Jimmy Brown',
      age: 16,
      address: 'New York No. 3 Lake Park',
    }],
  }, {
    key: 13,
    name: 'Jim Green sr.',
    age: 72,
    address: 'London No. 1 Lake Park',
    children: [{
      key: 131,
      name: 'Jim Green',
      age: 42,
      address: 'London No. 2 Lake Park',
      children: [{
        key: 1311,
        name: 'Jim Green jr.',
        age: 25,
        address: 'London No. 3 Lake Park',
      }, {
        key: 1312,
        name: 'Jimmy Green sr.',
        age: 18,
        address: 'London No. 4 Lake Park',
      }],
    }],
  }],
}, {
  key: 2,
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
}];

// rowSelection objects indicates the need for row selection
const rowSelection = {
  onChange: (selectedRowKeys, selectedRows) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  onSelect: (record, selected, selectedRows) => {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: (selected, selectedRows, changeRows) => {
    console.log(selected, selectedRows, changeRows);
  },
};

ReactDOM.render(
  <Table columns={columns} rowSelection={rowSelection} dataSource={data} />,
  mountNode
);
Name
Age
Address
Edward King 032London, Park Lane no. 0
Edward King 132London, Park Lane no. 1
Edward King 232London, Park Lane no. 2
Edward King 332London, Park Lane no. 3
Edward King 432London, Park Lane no. 4
Edward King 532London, Park Lane no. 5
Edward King 632London, Park Lane no. 6
Edward King 732London, Park Lane no. 7
Edward King 832London, Park Lane no. 8
Edward King 932London, Park Lane no. 9
Edward King 1032London, Park Lane no. 10
Edward King 1132London, Park Lane no. 11
Edward King 1232London, Park Lane no. 12
Edward King 1332London, Park Lane no. 13
Edward King 1432London, Park Lane no. 14
Edward King 1532London, Park Lane no. 15
Edward King 1632London, Park Lane no. 16
Edward King 1732London, Park Lane no. 17
Edward King 1832London, Park Lane no. 18
Edward King 1932London, Park Lane no. 19
Edward King 2032London, Park Lane no. 20
Edward King 2132London, Park Lane no. 21
Edward King 2232London, Park Lane no. 22
Edward King 2332London, Park Lane no. 23
Edward King 2432London, Park Lane no. 24
Edward King 2532London, Park Lane no. 25
Edward King 2632London, Park Lane no. 26
Edward King 2732London, Park Lane no. 27
Edward King 2832London, Park Lane no. 28
Edward King 2932London, Park Lane no. 29
Edward King 3032London, Park Lane no. 30
Edward King 3132London, Park Lane no. 31
Edward King 3232London, Park Lane no. 32
Edward King 3332London, Park Lane no. 33
Edward King 3432London, Park Lane no. 34
Edward King 3532London, Park Lane no. 35
Edward King 3632London, Park Lane no. 36
Edward King 3732London, Park Lane no. 37
Edward King 3832London, Park Lane no. 38
Edward King 3932London, Park Lane no. 39
Edward King 4032London, Park Lane no. 40
Edward King 4132London, Park Lane no. 41
Edward King 4232London, Park Lane no. 42
Edward King 4332London, Park Lane no. 43
Edward King 4432London, Park Lane no. 44
Edward King 4532London, Park Lane no. 45
Edward King 4632London, Park Lane no. 46
Edward King 4732London, Park Lane no. 47
Edward King 4832London, Park Lane no. 48
Edward King 4932London, Park Lane no. 49

Display large amounts of data in scrollable view.

Specify width of columns if header and cell do not align properly. (Leave one column at least without width to fit fluid layout)

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  width: 150,
}, {
  title: 'Age',
  dataIndex: 'age',
  width: 150,
}, {
  title: 'Address',
  dataIndex: 'address',
}];

const data = [];
for (let i = 0; i < 100; i++) {
  data.push({
    key: i,
    name: `Edward King ${i}`,
    age: 32,
    address: `London, Park Lane no. ${i}`,
  });
}

ReactDOM.render(
  <Table columns={columns} dataSource={data} pagination={{ pageSize: 50 }} scroll={{ y: 240 }} />,
  mountNode
);
Full Name
Age
Column 1
Column 2
Column 3
Column 4
Column 5
Column 6
Column 7
Column 8
Action
John Brown32New York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York Parkaction
Jim Green40London ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon Parkaction
Full Name
Age
John Brown32
Jim Green40

To fix some columns and scroll inside other columns, and you must set scroll.x meanwhile.

Specify the width of columns if header and cell do not align properly. (Leave one column at least without width to fit fluid layout)

A fixed value which is greater than table width for scroll.x is recommended. The sum of unfixed columns should not greater than scroll.x.

expand codeexpand code
import { Table } from 'antd';

const columns = [
  {
    title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left',
  },
  {
    title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left',
  },
  { title: 'Column 1', dataIndex: 'address', key: '1' },
  { title: 'Column 2', dataIndex: 'address', key: '2' },
  { title: 'Column 3', dataIndex: 'address', key: '3' },
  { title: 'Column 4', dataIndex: 'address', key: '4' },
  { title: 'Column 5', dataIndex: 'address', key: '5' },
  { title: 'Column 6', dataIndex: 'address', key: '6' },
  { title: 'Column 7', dataIndex: 'address', key: '7' },
  { title: 'Column 8', dataIndex: 'address', key: '8' },
  {
    title: 'Action',
    key: 'operation',
    fixed: 'right',
    width: 100,
    render: () => <a href="javascript:;">action</a>,
  },
];

const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York Park',
}, {
  key: '2',
  name: 'Jim Green',
  age: 40,
  address: 'London Park',
}];

ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />, mountNode);
Full Name
Age
Column 1
Column 2
Column 3
Column 4
Column 5
Column 6
Column 7
Column 8
Action
Edrward 032London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0action
Edrward 132London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1action
Edrward 232London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2action
Edrward 332London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3action
Edrward 432London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4action
Edrward 532London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5action
Edrward 632London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6action
Edrward 732London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7action
Edrward 832London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8action
Edrward 932London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9action
Full Name
Age
Edrward 032
Edrward 132
Edrward 232
Edrward 332
Edrward 432
Edrward 532
Edrward 632
Edrward 732
Edrward 832
Edrward 932

A Solution for displaying large amounts of data with long columns.

Specify the width of columns if header and cell do not align properly. (Leave one column at least without width to fit fluid layout)

A fixed value which is greater than table width for scroll.x is recommended. The sum of unfixed columns should not greater than scroll.x.

expand codeexpand code
import { Table } from 'antd';

const columns = [
  {
    title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left',
  },
  {
    title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left',
  },
  {
    title: 'Column 1', dataIndex: 'address', key: '1', width: 150,
  },
  {
    title: 'Column 2', dataIndex: 'address', key: '2', width: 150,
  },
  {
    title: 'Column 3', dataIndex: 'address', key: '3', width: 150,
  },
  {
    title: 'Column 4', dataIndex: 'address', key: '4', width: 150,
  },
  {
    title: 'Column 5', dataIndex: 'address', key: '5', width: 150,
  },
  {
    title: 'Column 6', dataIndex: 'address', key: '6', width: 150,
  },
  {
    title: 'Column 7', dataIndex: 'address', key: '7', width: 150,
  },
  { title: 'Column 8', dataIndex: 'address', key: '8' },
  {
    title: 'Action',
    key: 'operation',
    fixed: 'right',
    width: 100,
    render: () => <a href="javascript:;">action</a>,
  },
];

const data = [];
for (let i = 0; i < 100; i++) {
  data.push({
    key: i,
    name: `Edrward ${i}`,
    age: 32,
    address: `London Park no. ${i}`,
  });
}

ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1500, y: 300 }} />, mountNode);
Name
Other
Company
Gender
Age
Address
Company Address
Company Name
Street
Block
Building
Door No.
John Brown1Lake ParkC2035Lake Street 42SoftLake CoM
John Brown2Lake ParkC2035Lake Street 42SoftLake CoM
John Brown3Lake ParkC2035Lake Street 42SoftLake CoM
John Brown4Lake ParkC2035Lake Street 42SoftLake CoM
John Brown5Lake ParkC2035Lake Street 42SoftLake CoM
John Brown6Lake ParkC2035Lake Street 42SoftLake CoM
John Brown7Lake ParkC2035Lake Street 42SoftLake CoM
John Brown8Lake ParkC2035Lake Street 42SoftLake CoM
John Brown9Lake ParkC2035Lake Street 42SoftLake CoM
John Brown10Lake ParkC2035Lake Street 42SoftLake CoM
Name
John Brown
John Brown
John Brown
John Brown
John Brown
John Brown
John Brown
John Brown
John Brown
John Brown
Gender
M
M
M
M
M
M
M
M
M
M

Group table head with columns[n].children.

expand codeexpand code
import { Table } from 'antd';

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
  width: 100,
  fixed: 'left',
  filters: [{
    text: 'Joe',
    value: 'Joe',
  }, {
    text: 'John',
    value: 'John',
  }],
  onFilter: (value, record) => record.name.indexOf(value) === 0,
}, {
  title: 'Other',
  children: [{
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
    width: 200,
    sorter: (a, b) => a.age - b.age,
  }, {
    title: 'Address',
    children: [{
      title: 'Street',
      dataIndex: 'street',
      key: 'street',
      width: 200,
    }, {
      title: 'Block',
      children: [{
        title: 'Building',
        dataIndex: 'building',
        key: 'building',
        width: 100,
      }, {
        title: 'Door No.',
        dataIndex: 'number',
        key: 'number',
        width: 100,
      }],
    }],
  }],
}, {
  title: 'Company',
  children: [{
    title: 'Company Address',
    dataIndex: 'companyAddress',
    key: 'companyAddress',
  }, {
    title: 'Company Name',
    dataIndex: 'companyName',
    key: 'companyName',
  }],
}, {
  title: 'Gender',
  dataIndex: 'gender',
  key: 'gender',
  width: 80,
  fixed: 'right',
}];

const data = [];
for (let i = 0; i < 100; i++) {
  data.push({
    key: i,
    name: 'John Brown',
    age: i + 1,
    street: 'Lake Park',
    building: 'C',
    number: 2035,
    companyAddress: 'Lake Street 42',
    companyName: 'SoftLake Co',
    gender: 'M',
  });
}

ReactDOM.render(
  <Table
    columns={columns}
    dataSource={data}
    bordered
    size="middle"
    scroll={{ x: '130%', y: 240 }}
  />,
  mountNode
);
name
age
address
operation
Edward King 0
32London, Park Lane no. 0Delete
Edward King 1
32London, Park Lane no. 1Delete

Table with editable cells.

expand codeexpand code
import {
  Table, Input, Button, Popconfirm, Form,
} from 'antd';

const FormItem = Form.Item;
const EditableContext = React.createContext();

const EditableRow = ({ form, index, ...props }) => (
  <EditableContext.Provider value={form}>
    <tr {...props} />
  </EditableContext.Provider>
);

const EditableFormRow = Form.create()(EditableRow);

class EditableCell extends React.Component {
  state = {
    editing: false,
  }

  componentDidMount() {
    if (this.props.editable) {
      document.addEventListener('click', this.handleClickOutside, true);
    }
  }

  componentWillUnmount() {
    if (this.props.editable) {
      document.removeEventListener('click', this.handleClickOutside, true);
    }
  }

  toggleEdit = () => {
    const editing = !this.state.editing;
    this.setState({ editing }, () => {
      if (editing) {
        this.input.focus();
      }
    });
  }

  handleClickOutside = (e) => {
    const { editing } = this.state;
    if (editing && this.cell !== e.target && !this.cell.contains(e.target)) {
      this.save();
    }
  }

  save = () => {
    const { record, handleSave } = this.props;
    this.form.validateFields((error, values) => {
      if (error) {
        return;
      }
      this.toggleEdit();
      handleSave({ ...record, ...values });
    });
  }

  render() {
    const { editing } = this.state;
    const {
      editable,
      dataIndex,
      title,
      record,
      index,
      handleSave,
      ...restProps
    } = this.props;
    return (
      <td ref={node => (this.cell = node)} {...restProps}>
        {editable ? (
          <EditableContext.Consumer>
            {(form) => {
              this.form = form;
              return (
                editing ? (
                  <FormItem style={{ margin: 0 }}>
                    {form.getFieldDecorator(dataIndex, {
                      rules: [{
                        required: true,
                        message: `${title} is required.`,
                      }],
                      initialValue: record[dataIndex],
                    })(
                      <Input
                        ref={node => (this.input = node)}
                        onPressEnter={this.save}
                      />
                    )}
                  </FormItem>
                ) : (
                  <div
                    className="editable-cell-value-wrap"
                    style={{ paddingRight: 24 }}
                    onClick={this.toggleEdit}
                  >
                    {restProps.children}
                  </div>
                )
              );
            }}
          </EditableContext.Consumer>
        ) : restProps.children}
      </td>
    );
  }
}

class EditableTable extends React.Component {
  constructor(props) {
    super(props);
    this.columns = [{
      title: 'name',
      dataIndex: 'name',
      width: '30%',
      editable: true,
    }, {
      title: 'age',
      dataIndex: 'age',
    }, {
      title: 'address',
      dataIndex: 'address',
    }, {
      title: 'operation',
      dataIndex: 'operation',
      render: (text, record) => (
        this.state.dataSource.length >= 1
          ? (
            <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
              <a href="javascript:;">Delete</a>
            </Popconfirm>
          ) : null
      ),
    }];

    this.state = {
      dataSource: [{
        key: '0',
        name: 'Edward King 0',
        age: '32',
        address: 'London, Park Lane no. 0',
      }, {
        key: '1',
        name: 'Edward King 1',
        age: '32',
        address: 'London, Park Lane no. 1',
      }],
      count: 2,
    };
  }

  handleDelete = (key) => {
    const dataSource = [...this.state.dataSource];
    this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
  }

  handleAdd = () => {
    const { count, dataSource } = this.state;
    const newData = {
      key: count,
      name: `Edward King ${count}`,
      age: 32,
      address: `London, Park Lane no. ${count}`,
    };
    this.setState({
      dataSource: [...dataSource, newData],
      count: count + 1,
    });
  }

  handleSave = (row) => {
    const newData = [...this.state.dataSource];
    const index = newData.findIndex(item => row.key === item.key);
    const item = newData[index];
    newData.splice(index, 1, {
      ...item,
      ...row,
    });
    this.setState({ dataSource: newData });
  }

  render() {
    const { dataSource } = this.state;
    const components = {
      body: {
        row: EditableFormRow,
        cell: EditableCell,
      },
    };
    const columns = this.columns.map((col) => {
      if (!col.editable) {
        return col;
      }
      return {
        ...col,
        onCell: record => ({
          record,
          editable: col.editable,
          dataIndex: col.dataIndex,
          title: col.title,
          handleSave: this.handleSave,
        }),
      };
    });
    return (
      <div>
        <Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
          Add a row
        </Button>
        <Table
          components={components}
          rowClassName={() => 'editable-row'}
          bordered
          dataSource={dataSource}
          columns={columns}
        />
      </div>
    );
  }
}

ReactDOM.render(<EditableTable />, mountNode);
.editable-cell {
  position: relative;
}

.editable-cell-value-wrap {
  padding: 5px 12px;
  cursor: pointer;
}

.editable-row:hover .editable-cell-value-wrap {
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  padding: 4px 11px;
}
name
age
address
operation
Edrward 032London Park no. 0
Edrward 132London Park no. 1
Edrward 232London Park no. 2
Edrward 332London Park no. 3
Edrward 432London Park no. 4
Edrward 532London Park no. 5
Edrward 632London Park no. 6
Edrward 732London Park no. 7
Edrward 832London Park no. 8
Edrward 932London Park no. 9

Table with editable rows.

expand codeexpand code
import {
  Table, Input, InputNumber, Popconfirm, Form,
} from 'antd';

const data = [];
for (let i = 0; i < 100; i++) {
  data.push({
    key: i.toString(),
    name: `Edrward ${i}`,
    age: 32,
    address: `London Park no. ${i}`,
  });
}
const FormItem = Form.Item;
const EditableContext = React.createContext();

const EditableRow = ({ form, index, ...props }) => (
  <EditableContext.Provider value={form}>
    <tr {...props} />
  </EditableContext.Provider>
);

const EditableFormRow = Form.create()(EditableRow);

class EditableCell extends React.Component {
  getInput = () => {
    if (this.props.inputType === 'number') {
      return <InputNumber />;
    }
    return <Input />;
  };

  render() {
    const {
      editing,
      dataIndex,
      title,
      inputType,
      record,
      index,
      ...restProps
    } = this.props;
    return (
      <EditableContext.Consumer>
        {(form) => {
          const { getFieldDecorator } = form;
          return (
            <td {...restProps}>
              {editing ? (
                <FormItem style={{ margin: 0 }}>
                  {getFieldDecorator(dataIndex, {
                    rules: [{
                      required: true,
                      message: `Please Input ${title}!`,
                    }],
                    initialValue: record[dataIndex],
                  })(this.getInput())}
                </FormItem>
              ) : restProps.children}
            </td>
          );
        }}
      </EditableContext.Consumer>
    );
  }
}

class EditableTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = { data, editingKey: '' };
    this.columns = [
      {
        title: 'name',
        dataIndex: 'name',
        width: '25%',
        editable: true,
      },
      {
        title: 'age',
        dataIndex: 'age',
        width: '15%',
        editable: true,
      },
      {
        title: 'address',
        dataIndex: 'address',
        width: '40%',
        editable: true,
      },
      {
        title: 'operation',
        dataIndex: 'operation',
        render: (text, record) => {
          const editable = this.isEditing(record);
          return (
            <div>
              {editable ? (
                <span>
                  <EditableContext.Consumer>
                    {form => (
                      <a
                        href="javascript:;"
                        onClick={() => this.save(form, record.key)}
                        style={{ marginRight: 8 }}
                      >
                        Save
                      </a>
                    )}
                  </EditableContext.Consumer>
                  <Popconfirm
                    title="Sure to cancel?"
                    onConfirm={() => this.cancel(record.key)}
                  >
                    <a>Cancel</a>
                  </Popconfirm>
                </span>
              ) : (
                <a onClick={() => this.edit(record.key)}>Edit</a>
              )}
            </div>
          );
        },
      },
    ];
  }

  isEditing = record => record.key === this.state.editingKey;

  cancel = () => {
    this.setState({ editingKey: '' });
  };

  save(form, key) {
    form.validateFields((error, row) => {
      if (error) {
        return;
      }
      const newData = [...this.state.data];
      const index = newData.findIndex(item => key === item.key);
      if (index > -1) {
        const item = newData[index];
        newData.splice(index, 1, {
          ...item,
          ...row,
        });
        this.setState({ data: newData, editingKey: '' });
      } else {
        newData.push(row);
        this.setState({ data: newData, editingKey: '' });
      }
    });
  }

  edit(key) {
    this.setState({ editingKey: key });
  }

  render() {
    const components = {
      body: {
        row: EditableFormRow,
        cell: EditableCell,
      },
    };

    const columns = this.columns.map((col) => {
      if (!col.editable) {
        return col;
      }
      return {
        ...col,
        onCell: record => ({
          record,
          inputType: col.dataIndex === 'age' ? 'number' : 'text',
          dataIndex: col.dataIndex,
          title: col.title,
          editing: this.isEditing(record),
        }),
      };
    });

    return (
      <Table
        components={components}
        bordered
        dataSource={this.state.data}
        columns={columns}
        rowClassName="editable-row"
      />
    );
  }
}

ReactDOM.render(<EditableTable />, mountNode);
.editable-row .ant-form-explain {
  position: absolute;
  font-size: 12px;
  margin-top: -4px;
}
Name
Platform
Version
Upgraded
Creator
Date
Action
ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish
ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish
ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish

Showing more detailed info of every row.

expand codeexpand code
import {
  Table, Badge, Menu, Dropdown, Icon,
} from 'antd';

const menu = (
  <Menu>
    <Menu.Item>
      Action 1
    </Menu.Item>
    <Menu.Item>
      Action 2
    </Menu.Item>
  </Menu>
);

function NestedTable() {
  const expandedRowRender = () => {
    const columns = [
      { title: 'Date', dataIndex: 'date', key: 'date' },
      { title: 'Name', dataIndex: 'name', key: 'name' },
      { title: 'Status', key: 'state', render: () => <span><Badge status="success" />Finished</span> },
      { title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
      {
        title: 'Action',
        dataIndex: 'operation',
        key: 'operation',
        render: () => (
          <span className="table-operation">
            <a href="javascript:;">Pause</a>
            <a href="javascript:;">Stop</a>
            <Dropdown overlay={menu}>
              <a href="javascript:;">
                More <Icon type="down" />
              </a>
            </Dropdown>
          </span>
        ),
      },
    ];

    const data = [];
    for (let i = 0; i < 3; ++i) {
      data.push({
        key: i,
        date: '2014-12-24 23:12:00',
        name: 'This is production name',
        upgradeNum: 'Upgraded: 56',
      });
    }
    return (
      <Table
        columns={columns}
        dataSource={data}
        pagination={false}
      />
    );
  };

  const columns = [
    { title: 'Name', dataIndex: 'name', key: 'name' },
    { title: 'Platform', dataIndex: 'platform', key: 'platform' },
    { title: 'Version', dataIndex: 'version', key: 'version' },
    { title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
    { title: 'Creator', dataIndex: 'creator', key: 'creator' },
    { title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
    { title: 'Action', key: 'operation', render: () => <a href="javascript:;">Publish</a> },
  ];

  const data = [];
  for (let i = 0; i < 3; ++i) {
    data.push({
      key: i,
      name: 'Screem',
      platform: 'iOS',
      version: '10.3.4.5654',
      upgradeNum: 500,
      creator: 'Jack',
      createdAt: '2014-12-24 23:12:00',
    });
  }

  return (
    <Table
      className="components-table-demo-nested"
      columns={columns}
      expandedRowRender={expandedRowRender}
      dataSource={data}
    />
  );
}

ReactDOM.render(<NestedTable />, mountNode);
Name
Age
Address

By using custom components, we can integrate table with react-dnd to implement drag sorting.

expand codeexpand code
import { Table } from 'antd';
import { DragDropContext, DragSource, DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import update from 'immutability-helper';

let dragingIndex = -1;

class BodyRow extends React.Component {
  render() {
    const {
      isOver,
      connectDragSource,
      connectDropTarget,
      moveRow,
      ...restProps
    } = this.props;
    const style = { ...restProps.style, cursor: 'move' };

    let className = restProps.className;
    if (isOver) {
      if (restProps.index > dragingIndex) {
        className += ' drop-over-downward';
      }
      if (restProps.index < dragingIndex) {
        className += ' drop-over-upward';
      }
    }

    return connectDragSource(
      connectDropTarget(
        <tr
          {...restProps}
          className={className}
          style={style}
        />
      )
    );
  }
}

const rowSource = {
  beginDrag(props) {
    dragingIndex = props.index;
    return {
      index: props.index,
    };
  },
};

const rowTarget = {
  drop(props, monitor) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
      return;
    }

    // Time to actually perform the action
    props.moveRow(dragIndex, hoverIndex);

    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex;
  },
};

const DragableBodyRow = DropTarget(
  'row',
  rowTarget,
  (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
  }),
)(
  DragSource(
    'row',
    rowSource,
    (connect) => ({
      connectDragSource: connect.dragSource(),
    }),
  )(BodyRow),
);

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
}, {
  title: 'Age',
  dataIndex: 'age',
  key: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
  key: 'address',
}];

class DragSortingTable extends React.Component {
  state = {
    data: [{
      key: '1',
      name: 'John Brown',
      age: 32,
      address: 'New York No. 1 Lake Park',
    }, {
      key: '2',
      name: 'Jim Green',
      age: 42,
      address: 'London No. 1 Lake Park',
    }, {
      key: '3',
      name: 'Joe Black',
      age: 32,
      address: 'Sidney No. 1 Lake Park',
    }],
  }

  components = {
    body: {
      row: DragableBodyRow,
    },
  }

  moveRow = (dragIndex, hoverIndex) => {
    const { data } = this.state;
    const dragRow = data[dragIndex];

    this.setState(
      update(this.state, {
        data: {
          $splice: [[dragIndex, 1], [hoverIndex, 0, dragRow]],
        },
      }),
    );
  }

  render() {
    return (
      <Table
        columns={columns}
        dataSource={this.state.data}
        components={this.components}
        onRow={(record, index) => ({
          index,
          moveRow: this.moveRow,
        })}
      />
    );
  }
}

const Demo = DragDropContext(HTML5Backend)(DragSortingTable);

ReactDOM.render(<Demo />, mountNode);
#components-table-demo-drag-sorting tr.drop-over-downward td {
  border-bottom: 2px dashed #1890ff;
}

#components-table-demo-drag-sorting tr.drop-over-upward td {
  border-top: 2px dashed #1890ff;
}
Date
Amount
Type
Note
Action
2018-02-11120incometransferDelete
2018-03-11243incometransferDelete
2018-04-1198incometransferDelete

Implement resizable column by integrate with react-resizable.

expand codeexpand code
import { Table } from 'antd';
import { Resizable } from 'react-resizable';

const ResizeableTitle = (props) => {
  const { onResize, width, ...restProps } = props;

  if (!width) {
    return <th {...restProps} />;
  }

  return (
    <Resizable width={width} height={0} onResize={onResize}>
      <th {...restProps} />
    </Resizable>
  );
};

class Demo extends React.Component {
  state = {
    columns: [{
      title: 'Date',
      dataIndex: 'date',
      width: 200,
    }, {
      title: 'Amount',
      dataIndex: 'amount',
      width: 100,
    }, {
      title: 'Type',
      dataIndex: 'type',
      width: 100,
    }, {
      title: 'Note',
      dataIndex: 'note',
      width: 100,
    }, {
      title: 'Action',
      key: 'action',
      render: () => (
        <a href="javascript:;">Delete</a>
      ),
    }],
  };

  components = {
    header: {
      cell: ResizeableTitle,
    },
  };

  data = [{
    key: 0,
    date: '2018-02-11',
    amount: 120,
    type: 'income',
    note: 'transfer',
  }, {
    key: 1,
    date: '2018-03-11',
    amount: 243,
    type: 'income',
    note: 'transfer',
  }, {
    key: 2,
    date: '2018-04-11',
    amount: 98,
    type: 'income',
    note: 'transfer',
  }];

  handleResize = index => (e, { size }) => {
    this.setState(({ columns }) => {
      const nextColumns = [...columns];
      nextColumns[index] = {
        ...nextColumns[index],
        width: size.width,
      };
      return { columns: nextColumns };
    });
  };

  render() {
    const columns = this.state.columns.map((col, index) => ({
      ...col,
      onHeaderCell: column => ({
        width: column.width,
        onResize: this.handleResize(index),
      }),
    }));

    return (
      <Table
        bordered
        components={this.components}
        columns={columns}
        dataSource={this.data}
      />
    );
  }
}


ReactDOM.render(<Demo />, mountNode);
#components-table-demo-resizable-column .react-resizable {
  position: relative;
}

#components-table-demo-resizable-column .react-resizable-handle {
  position: absolute;
  width: 10px;
  height: 100%;
  bottom: 0;
  right: -5px;
  cursor: col-resize;
}

Select different settings to see the result.

expand codeexpand code
import {
  Table, Icon, Switch, Radio, Form, Divider,
} from 'antd';

const FormItem = Form.Item;

const columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
  width: 150,
  render: text => <a href="javascript:;">{text}</a>,
}, {
  title: 'Age',
  dataIndex: 'age',
  key: 'age',
  width: 70,
}, {
  title: 'Address',
  dataIndex: 'address',
  key: 'address',
}, {
  title: 'Action',
  key: 'action',
  width: 360,
  render: (text, record) => (
    <span>
      <a href="javascript:;">Action 一 {record.name}</a>
      <Divider type="vertical" />
      <a href="javascript:;">Delete</a>
      <Divider type="vertical" />
      <a href="javascript:;" className="ant-dropdown-link">
        More actions <Icon type="down" />
      </a>
    </span>
  ),
}];

const data = [];
for (let i = 1; i <= 10; i++) {
  data.push({
    key: i,
    name: 'John Brown',
    age: `${i}2`,
    address: `New York No. ${i} Lake Park`,
    description: `My name is John Brown, I am ${i}2 years old, living in New York No. ${i} Lake Park.`,
  });
}

const expandedRowRender = record => <p>{record.description}</p>;
const title = () => 'Here is title';
const showHeader = true;
const footer = () => 'Here is footer';
const scroll = { y: 240 };
const pagination = { position: 'bottom' };

class Demo extends React.Component {
  state = {
    bordered: false,
    loading: false,
    pagination,
    size: 'default',
    expandedRowRender,
    title: undefined,
    showHeader,
    footer,
    rowSelection: {},
    scroll: undefined,
    hasData: true,
  }

  handleToggle = prop => (enable) => {
    this.setState({ [prop]: enable });
  }

  handleSizeChange = (e) => {
    this.setState({ size: e.target.value });
  }

  handleExpandChange = (enable) => {
    this.setState({ expandedRowRender: enable ? expandedRowRender : undefined });
  }

  handleTitleChange = (enable) => {
    this.setState({ title: enable ? title : undefined });
  }

  handleHeaderChange = (enable) => {
    this.setState({ showHeader: enable ? showHeader : false });
  }

  handleFooterChange = (enable) => {
    this.setState({ footer: enable ? footer : undefined });
  }

  handleRowSelectionChange = (enable) => {
    this.setState({ rowSelection: enable ? {} : undefined });
  }

  handleScollChange = (enable) => {
    this.setState({ scroll: enable ? scroll : undefined });
  }

  handleDataChange = (hasData) => {
    this.setState({ hasData });
  }

  handlePaginationChange = (e) => {
    const { value } = e.target;
    this.setState({
      pagination: value === 'none' ? false : { position: value },
    });
  }

  render() {
    const state = this.state;
    return (
      <div>
        <div className="components-table-demo-control-bar">
          <Form layout="inline">
            <FormItem label="Bordered">
              <Switch checked={state.bordered} onChange={this.handleToggle('bordered')} />
            </FormItem>
            <FormItem label="loading">
              <Switch checked={state.loading} onChange={this.handleToggle('loading')} />
            </FormItem>
            <FormItem label="Title">
              <Switch checked={!!state.title} onChange={this.handleTitleChange} />
            </FormItem>
            <FormItem label="Column Header">
              <Switch checked={!!state.showHeader} onChange={this.handleHeaderChange} />
            </FormItem>
            <FormItem label="Footer">
              <Switch checked={!!state.footer} onChange={this.handleFooterChange} />
            </FormItem>
            <FormItem label="Expandable">
              <Switch checked={!!state.expandedRowRender} onChange={this.handleExpandChange} />
            </FormItem>
            <FormItem label="Checkbox">
              <Switch checked={!!state.rowSelection} onChange={this.handleRowSelectionChange} />
            </FormItem>
            <FormItem label="Fixed Header">
              <Switch checked={!!state.scroll} onChange={this.handleScollChange} />
            </FormItem>
            <FormItem label="Has Data">
              <Switch checked={!!state.hasData} onChange={this.handleDataChange} />
            </FormItem>
            <FormItem label="Size">
              <Radio.Group size="default" value={state.size} onChange={this.handleSizeChange}>
                <Radio.Button value="default">Default</Radio.Button>
                <Radio.Button value="middle">Middle</Radio.Button>
                <Radio.Button value="small">Small</Radio.Button>
              </Radio.Group>
            </FormItem>
            <FormItem label="Pagination">
              <Radio.Group
                value={state.pagination ? state.pagination.position : 'none'}
                onChange={this.handlePaginationChange}
              >
                <Radio.Button value="top">Top</Radio.Button>
                <Radio.Button value="bottom">Bottom</Radio.Button>
                <Radio.Button value="both">Both</Radio.Button>
                <Radio.Button value="none">None</Radio.Button>
              </Radio.Group>
            </FormItem>
          </Form>
        </div>
        <Table {...this.state} columns={columns} dataSource={state.hasData ? data : null} />
      </div>
    );
  }
}

ReactDOM.render(<Demo />, mountNode);

API#

Table#

PropertyDescriptionTypeDefault
borderedWhether to show all table bordersbooleanfalse
childrenColumnNameThe column contains children to displaystring[]children
columnsColumns of tableColumnProps[]-
componentsOverride default table elementsTableComponents-
dataSourceData record array to be displayedany[]-
defaultExpandAllRowsExpand all rows initiallybooleanfalse
defaultExpandedRowKeysInitial expanded row keysstring[]-
expandedRowKeysCurrent expanded row keysstring[]-
expandedRowRenderExpanded container render for each rowFunction(record, index, indent, expanded):ReactNode-
expandIconCustomize row expand Icon. Ref exampleFunction(props):ReactNode-
expandRowByClickWhether to expand row by clicking anywhere in the whole rowbooleanfalse
footerTable footer rendererFunction(currentPageData)
indentSizeIndent size in pixels of tree datanumber15
loadingLoading status of tableboolean|object (more)false
localei18n text including filter, sort, empty text, etcobjectfilterConfirm: 'Ok'
filterReset: 'Reset'
emptyText: 'No Data'
Default
paginationConfig of pagination. You can ref table pagination config or full pagination document, hide it by setting it to falseobject
rowClassNameRow's classNameFunction(record, index):string-
rowKeyRow's unique key, could be a string or function that returns a stringstring|Function(record):stringkey
rowSelectionRow selection configobjectnull
scrollSet horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area. It is recommended to set a number for x, if you want to set it to true, you need to add style .ant-table td { white-space: nowrap; }.{ x: number | true, y: number }-
showHeaderWhether to show table headerbooleantrue
sizeSize of tabledefault | middle | smalldefault
titleTable title rendererFunction(currentPageData)
onChangeCallback executed when pagination, filters or sorter is changedFunction(pagination, filters, sorter, extra: { currentDataSource: [] })
onExpandCallback executed when the row expand icon is clickedFunction(expanded, record)
onExpandedRowsChangeCallback executed when the expanded rows changeFunction(expandedRows)
onHeaderRowSet props on per header rowFunction(column, index)-
onRowSet props on per rowFunction(record, index)-

onRow usage#

Same as onRow onHeaderRow onCell onHeaderCell

<Table
  onRow={(record, rowIndex) => {
    return {
      onClick: (event) => {},       // click row
      onDoubleClick: (event) => {}, // double click row
      onContextMenu: (event) => {}  // right button click row
      onMouseEnter: (event) => {}   // mouse enter row
      onMouseLeave: (event) => {}   // mouse leave row
    };
  }}
  onHeaderRow={(column) => {
    return {
      onClick: () => {},        // click header row
    };
  }}
/>

Column#

One of the Table columns prop for describing the table's columns, Column has the same API.

PropertyDescriptionTypeDefault
alignspecify how content is aligned'left' | 'right' | 'center''left'
classNameclassName of this columnstring-
colSpanSpan of this column's titlenumber
dataIndexDisplay field of the data record, could be set like a.b.c, a[0].b.c[1]string-
defaultSortOrderDefault order of sorted values'ascend' | 'descend'-
filterDropdownCustomized filter overlayReactNode-
filterDropdownVisibleWhether filterDropdown is visibleboolean-
filteredWhether the dataSource is filteredbooleanfalse
filteredValueControlled filtered value, filter icon will highlightstring[]-
filterIconCustomized filter iconReactNode|(filtered: boolean) => ReactNodefalse
filterMultipleWhether multiple filters can be selectedbooleantrue
filtersFilter menu configobject[]-
fixedSet column to be fixed: true(same as left) 'left' 'right'boolean|stringfalse
keyUnique key of this column, you can ignore this prop if you've set a unique dataIndexstring-
renderRenderer of the table cell. The return value should be a ReactNode, or an object for colSpan/rowSpan configFunction(text, record, index) {}-
sorterSort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to trueFunction|boolean-
sortOrderOrder of sorted values: 'ascend' 'descend' falseboolean|string-
titleTitle of this columnReactNode|({ sortOrder, filters }) => ReactNode-
widthWidth of this columnstring|number-
onCellSet props on per cellFunction(record, rowIndex)-
onFilterCallback executed when the confirm filter button is clickedFunction-
onFilterDropdownVisibleChangeCallback executed when filterDropdownVisible is changedfunction(visible) {}-
onHeaderCellSet props on per header cellFunction(column)-

ColumnGroup#

PropertyDescriptionTypeDefault
titleTitle of the column groupstring|ReactNode-

pagination#

Properties for pagination.

PropertyDescriptionTypeDefault
positionspecify the position of Pagination'top' | 'bottom' | 'both''bottom'

More about pagination, please check Pagination.

rowSelection#

Properties for row selection.

PropertyDescriptionTypeDefault
columnWidthSet the width of the selection columnstring|number-
columnTitleSet the title of the selection columnstring|React.ReactNode-
fixedFixed selection column on the leftboolean-
getCheckboxPropsGet Checkbox or Radio propsFunction(record)-
hideDefaultSelectionsRemove the default Select All and Select Invert selectionsbooleanfalse
selectedRowKeysControlled selected row keysstring[][]
selectionsCustom selection config, only displays default selections when set to trueobject[]|boolean-
typecheckbox or radiocheckbox | radiocheckbox
onChangeCallback executed when selected rows changeFunction(selectedRowKeys, selectedRows)-
onSelectCallback executed when select/deselect one rowFunction(record, selected, selectedRows, nativeEvent)-
onSelectAllCallback executed when select/deselect all rowsFunction(selected, selectedRows, changeRows)-
onSelectInvertCallback executed when row selection is invertedFunction(selectedRows)-

selection#

PropertyDescriptionTypeDefault
keyUnique key of this selectionstring-
textDisplay text of this selectionstring|React.ReactNode-
onSelectCallback executed when this selection is clickedFunction(changeableRowKeys)-

Using in TypeScript#

import { Table } from 'antd';
import { ColumnProps } from 'antd/lib/table';

interface IUser {
  key: number,
  name: string;
}

const columns: ColumnProps<IUser>[] = [{
  key: 'name',
  title: 'Name',
  dataIndex: 'name',
}];

const data: IUser[] = [{
  key: 0,
  name: 'Jack',
}];

class UserTable extends Table<IUser> {}

<UserTable columns={columns} dataSource={data} />

// Use JSX style API
class NameColumn extends Table.Column<IUser> {}

<UserTable dataSource={data}>
  <NameColumn key="name" title="Name" dataIndex="name" />
</UserTable>

// after TypeScript 2.9 can write like this
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#generic-type-arguments-in-jsx-elements
<Table<IUser> columns={columns} dataSource={data} />
<Table<IUser> dataSource={data}>
  <Table.Column<IUser> key="name" title="Name" dataIndex="name" />
</Table>

Note#

According to React documentation, every child in array should be assigned a unique key. The values inside dataSource and columns should follow this in Table, and dataSource[i].key would be treated as key value default for dataSource.

If dataSource[i].key is not provided, then you should specify the primary key of dataSource value via rowKey. If not, warnings like above will show in browser console.

console warning

// primary key is uid
return <Table rowKey="uid" />;
// or
return <Table rowKey={record => record.uid} />;
TabsAlert