Bug fixes and readme updates

This commit is contained in:
Tanner Linsley
2017-01-11 19:58:52 -07:00
parent 1cb280942d
commit 3c5ba2843d
7 changed files with 211 additions and 183 deletions
+34 -35
View File
@@ -24,7 +24,7 @@
- [Installation](#installation)
- [Example](#example)
- [Data](#data)
- [Default Props](#default-props)
- [Props](#props)
- [Columns](#columns)
- [Styles](#styles)
- [Header Groups](#header-groups)
@@ -33,13 +33,11 @@
- [Multi-sort](#multi-sort)
- [Component Overrides](#component-overrides)
<a name="installation"></a>
## Installation
```bash
$ npm install react-table
```
<a name="example"></a>
## Example
```javascript
import ReactTable from 'react-table'
@@ -76,19 +74,17 @@ const columns = [{
/>
```
<a name="data"></a>
## Data
Simply pass the `data` prop anything that resembles an array or object. Client-side sorting and pagination are built in, and your table will update gracefully as you change any props. [Server-side data](#server-side-data) is also supported!
<a name="default-props"></a>
## Default Props
These are the default props for the main react component `<ReactTable />`
## Props
These are all of the available props (and their default values) for the main `<ReactTable />` component.
```javascript
{
// General
loading: false, // Whether to show the loading overlay or not
pageSize: 20, // The default page size (this can be changed by the user if `showPageSizeOptions` is enabled)
defaultPageSize: 20, // The default page size (this can be changed by the user if `showPageSizeOptions` is enabled)
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
showPagination: true, // Shows or hides the pagination component
showPageJump: true, // Shows or hides the pagination number input
@@ -115,6 +111,7 @@ These are the default props for the main react component `<ReactTable />`
trClassName: '', // ClassName for all `tr` elements
trClassCallback: row => null, // A call back to dynamically add classes (via the classnames module) to a row element
paginationClassName: '' // ClassName for `pagination` element
// Styles
style: {}, // Main style object for the component
tableStyle: {}, // style object for the `table` component
@@ -125,6 +122,12 @@ These are the default props for the main react component `<ReactTable />`
thStyle: {}, // style object for the `th` component
tdStyle: {}, // style object for the `td` component
paginationStyle: {}, // style object for the `paginination` component
// Controlled State (see Using as a Fully Controlled Component below)
page: undefined,
pageSize: undefined,
sorting: undefined,
visibleSubComponents: undefined
}
```
@@ -134,7 +137,7 @@ You can easily override the core defaults like so:
import { ReactTableDefaults } from 'react-table'
Object.assign(ReactTableDefaults, {
pageSize: 10,
defaultPageSize: 10,
minRows: 3,
// etc...
})
@@ -144,15 +147,14 @@ Or just define them on the component per-instance
```javascript
<ReactTable
pageSize={10}
defaultPageSize={10}
minRows={3}
// etc...
/>
```
<a name="columns"></a>
## Columns
`<ReactTable/>` requires a `columns` prop, which is an array of objects with the following properties
`<ReactTable/>` requires a `columns` prop, which is an array of objects containing the following properties
```javascript
[{
@@ -162,7 +164,7 @@ Or just define them on the component per-instance
sortable: true,
sort: 'asc' or 'desc', // used to determine the column sorting on init
show: true, // can be used to hide a column
minWidth: 100 // A minimum width for this column. If there is room, columns will flex to fill
minWidth: 100 // A minimum width for this column. If there is room, columns will flex to fill available space
// Cell Options
className: '', // Set the classname of the `td` element of the column
@@ -185,11 +187,9 @@ Or just define them on the component per-instance
}]
```
<a name="styles"></a>
## Styles
React-table is built to be dropped into existing applications or styled from the ground up, but if you'd like a decent starting point, you can optionally include our default theme `react-table.css`. We think it looks great, honestly :)
<a name="header-groups"></a>
## Header Groups
To group columns with another header column, just nest your columns in a header column like so:
```javascript
@@ -209,13 +209,13 @@ const columns = [{
```
## Sub Tables & Components
By adding a `subComponent` props, you can easily add an expansion level to all root-level rows:
By adding a `SubComponent` props, you can easily add an expansion level to all root-level rows:
```javascript
<ReactTable
data={data}
columns={columns}
pageSize={10}
subComponent={(row) => {
defaultPageSize={10}
SubComponent={(row) => {
return (
<div>
You can put any component you want here, even another React Table! You even have access to the row-level data if you need! Spark-charts, drill-throughs, infographics... the possibilities are endless!
@@ -226,7 +226,6 @@ By adding a `subComponent` props, you can easily add an expansion level to all r
```
<a name="server-side-data"></a>
## Server-side Data
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
@@ -267,38 +266,38 @@ If you want to handle pagination, and sorting on the server, `react-table` makes
For a detailed example, take a peek at our [async table mockup](https://github.com/tannerlinsley/react-table/blob/master/example/src/screens/async.js)
<a name="multi-sort"></a>
## Multi-Sort
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
<a name="component-overrides"></a>
## Component Overrides
Though we wouldn't suggest it, `react-table` has the ability to change the core componentry it used to render it's table. You can do so by assigning a react component to it's corresponding global prop, or on a one-off basis like so:
Though we confidently stand by the markup and architecture behind it, `react-table` does offer the ability to change the core componentry it uses to render everything. You can extend or override these internal components by passing a react component to it's corresponding prop on either the global props or on a one-off basis like so:
```javascript
// Change the global default
import { ReactTableDefaults } from 'react-table'
Object.assign(ReactTableDefaults, {
tableComponent: Component,
theadComponent: Component,
tbodyComponent: Component,
trComponent: Component,
thComponent: Component,
tdComponent: Component,
paginationComponent: Component,
previousComponent: Component,
nextComponent: Component,
loadingComponent: Component
TableComponent: Component,
TheadComponent: Component,
TbodyComponent: Component,
TrGroupComponent: Component,
TrComponent: Component,
ThComponent: Component,
TdComponent: Component,
PaginationComponent: Component,
PreviousComponent: Component,
NextComponent: Component,
LoadingComponent: Component,
ExpanderComponent: Component
})
// Or change per instance
<ReactTable
tableComponent={Component},
theadComponent={Component},
TableComponent={Component},
TheadComponent={Component},
// etc...
/>
```
If you choose to change the core components React-Table uses to render, you must make sure your components support and utilized all of the neeeded features for that component to work properly. For a broad reference on how to do this, investigate [the source](https://github.com/tannerlinsley/react-table/blob/master/src/index.js) for the component you wish to replace.
If you choose to change the core components React-Table uses to render, you must make sure your replacement components consume and utilize all of the supplied and inherited props that are needed for that component to function properly. We would suggest investigating [the source](https://github.com/tannerlinsley/react-table/blob/master/src/index.js) for the component you wish to replace.
## Contributing
+147 -133
View File
@@ -6,25 +6,23 @@ import _ from './utils'
import Pagination from './pagination'
const makeTemplateComponent = (compClass) => ({children, className, ...rest}) => (
<div
className={classnames(compClass, className)}
{...rest}
>
{children}
</div>
)
export const ReactTableDefaults = {
// General
data: [],
loading: false,
pageSize: 20,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 30,
// State Overrides (for controlled-component style)
// page
// pageSize
// sorting
// visibleSubComponents
// Callbacks
onChange: () => null,
onTrClick: () => null,
@@ -69,12 +67,12 @@ export const ReactTableDefaults = {
ofText: 'of',
rowsText: 'rows',
// Components
tableComponent: makeTemplateComponent('rt-table'),
theadComponent: makeTemplateComponent('rt-thead'),
tbodyComponent: makeTemplateComponent('rt-tbody'),
trGroupComponent: makeTemplateComponent('rt-tr-group'),
trComponent: makeTemplateComponent('rt-tr'),
thComponent: ({toggleSort, className, children, ...rest}) => {
TableComponent: _.makeTemplateComponent('rt-table'),
TheadComponent: _.makeTemplateComponent('rt-thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
TrComponent: _.makeTemplateComponent('rt-tr'),
ThComponent: ({toggleSort, className, children, ...rest}) => {
return (
<div
className={classnames(className, 'rt-th')}
@@ -87,8 +85,8 @@ export const ReactTableDefaults = {
</div>
)
},
tdComponent: makeTemplateComponent('rt-td'),
expanderComponent: ({isOpen, toggle, ...rest}) => {
TdComponent: _.makeTemplateComponent('rt-td'),
ExpanderComponent: ({isOpen, toggle, ...rest}) => {
return (
<div
className={classnames('rt-expander', isOpen && '-open')}
@@ -96,13 +94,13 @@ export const ReactTableDefaults = {
/>
)
},
paginationComponent: Pagination,
previousComponent: null,
nextComponent: null,
loadingComponent: props => (
<div className={classnames('-loading', {'-active': props.loading})}>
PaginationComponent: Pagination,
PreviousComponent: null,
NextComponent: null,
LoadingComponent: ({loading, loadingText}) => (
<div className={classnames('-loading', {'-active': loading})}>
<div className='-loading-inner'>
{props.loadingText}
{loadingText}
</div>
</div>
)
@@ -112,91 +110,21 @@ export default React.createClass({
getDefaultProps () {
return ReactTableDefaults
},
getInitialState () {
return {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: false,
visibleSubComponents: {}
}
},
componentDidMount () {
this.fireOnChange()
},
fireOnChange () {
this.props.onChange({
page: this.getPropOrState('page'),
pageSize: this.getStateOrProp('pageSize'),
pages: this.getPagesLength(),
sorting: this.getSorting()
}, this)
},
getPropOrState (key) {
return _.getFirstDefined(this.props[key], this.state[key])
},
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
getInitSorting (columns) {
if (!columns) {
return []
}
const initSorting = columns.filter(d => {
return typeof d.sort !== 'undefined'
}).map(d => {
return {
id: d.id,
asc: d.sort === 'asc'
}
})
return initSorting.length ? initSorting : [{
id: columns[0].id,
asc: true
}]
},
sortData (data, sorting) {
return _.orderBy(data, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
}
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
}
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
},
makeDecoratedColumn (column) {
const dcol = Object.assign({}, this.props.column, column)
if (typeof dcol.accessor === 'string') {
dcol.id = dcol.id || dcol.accessor
const accessorString = dcol.accessor
dcol.accessor = row => _.get(row, accessorString)
return dcol
}
if (dcol.accessor && !dcol.id) {
console.warn(dcol)
throw new Error('A column id is required if using a non-string accessor for column above.')
}
if (!dcol.accessor) {
dcol.accessor = d => undefined
}
return dcol
},
getSorting (columns) {
return this.props.sorting || (this.state.sorting && this.state.sorting.length ? this.state.sorting : this.getInitSorting(columns))
},
getPagesLength () {
return this.props.manual ? this.props.pages
: Math.ceil(this.props.data.length / this.getStateOrProp('pageSize'))
},
getMinRows () {
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
},
render () {
const {visibleSubComponents} = this.state
const {
className,
style,
@@ -209,6 +137,7 @@ export default React.createClass({
thClassname,
thStyle,
data,
columns,
theadClassName,
tbodyClassName,
tbodyStyle,
@@ -226,8 +155,29 @@ export default React.createClass({
ofText,
rowsText,
paginationClassName,
expanderColumnWidth
} = this.props
expanderColumnWidth,
manual,
loadingText,
// State
visibleSubComponents,
loading,
pageSize,
page,
// Components
TableComponent,
TheadComponent,
TbodyComponent,
TrGroupComponent,
TrComponent,
ThComponent,
TdComponent,
ExpanderComponent,
PaginationComponent,
PreviousComponent,
NextComponent,
LoadingComponent,
SubComponent
} = this.getResolvedState()
// Build Columns
const decoratedColumns = []
@@ -236,8 +186,7 @@ export default React.createClass({
// Determine Header Groups
let hasHeaderGroups = false
this.props.columns
.forEach(column => {
columns.forEach(column => {
if (column.columns) {
hasHeaderGroups = true
}
@@ -252,7 +201,7 @@ export default React.createClass({
}
// Build the columns and headers
const visibleColumns = this.props.columns.filter(d => _.getFirstDefined(d.show, true))
const visibleColumns = columns.filter(d => _.getFirstDefined(d.show, true))
visibleColumns.forEach((column, i) => {
if (column.columns) {
const nestedColumns = column.columns.filter(d => _.getFirstDefined(d.show, true))
@@ -278,7 +227,7 @@ export default React.createClass({
}
const sorting = this.getSorting(decoratedColumns)
const accessedData = this.props.data.map((d, i) => {
const accessedData = data.map((d, i) => {
const row = {
__original: d,
__index: i
@@ -288,38 +237,22 @@ export default React.createClass({
})
return row
})
const resolvedData = this.props.manual ? accessedData : this.sortData(accessedData, sorting)
const resolvedData = manual ? accessedData : this.sortData(accessedData, sorting)
// Normalize state
const currentPage = this.getPropOrState('page')
const pageSize = this.getStateOrProp('pageSize')
const pagesLength = this.getPagesLength()
// Pagination
const startRow = pageSize * currentPage
const startRow = pageSize * page
const endRow = startRow + pageSize
const pageRows = this.props.manual ? resolvedData : resolvedData.slice(startRow, endRow)
const pageRows = manual ? resolvedData : resolvedData.slice(startRow, endRow)
const minRows = this.getMinRows()
const padRows = pagesLength > 1 ? _.range(pageSize - pageRows.length)
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: []
const canPrevious = currentPage > 0
const canNext = currentPage + 1 < pagesLength
const TableComponent = this.props.tableComponent
const TheadComponent = this.props.theadComponent
const TbodyComponent = this.props.tbodyComponent
const TrGroupComponent = this.props.trGroupComponent
const TrComponent = this.props.trComponent
const ThComponent = this.props.thComponent
const TdComponent = this.props.tdComponent
const ExpanderComponent = this.props.expanderComponent
const PaginationComponent = this.props.paginationComponent
const PreviousComponent = this.props.previousComponent
const NextComponent = this.props.nextComponent
const LoadingComponent = this.props.loadingComponent
const SubComponent = this.props.subComponent
const canPrevious = page > 0
const canNext = page + 1 < pagesLength
const rowWidth = (SubComponent ? expanderColumnWidth : 0) + _.sum(decoratedColumns.map(d => d.minWidth))
@@ -535,7 +468,7 @@ export default React.createClass({
</TableComponent>
{showPagination && (
<PaginationComponent
currentPage={currentPage}
page={page}
pagesLength={pagesLength}
pageSize={pageSize}
showPageSizeOptions={showPageSizeOptions}
@@ -557,10 +490,92 @@ export default React.createClass({
className={paginationClassName}
/>
)}
<LoadingComponent {...this.props} />
<LoadingComponent
loading={loading}
loadingText={loadingText}
/>
</div>
)
},
// Helpers
getResolvedState () {
return {
...this.state,
...this.props,
pages: this.getPagesLength(),
sorting: this.getSorting()
}
},
fireOnChange () {
this.props.onChange(this.getResolvedState(), this)
},
getPropOrState (key) {
return _.getFirstDefined(this.props[key], this.state[key])
},
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
getInitSorting (columns) {
if (!columns) {
return []
}
const initSorting = columns.filter(d => {
return typeof d.sort !== 'undefined'
}).map(d => {
return {
id: d.id,
asc: d.sort === 'asc'
}
})
return initSorting.length ? initSorting : [{
id: columns[0].id,
asc: true
}]
},
sortData (data, sorting) {
return _.orderBy(data, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
}
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
}
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
},
makeDecoratedColumn (column) {
const dcol = Object.assign({}, this.props.column, column)
if (typeof dcol.accessor === 'string') {
dcol.id = dcol.id || dcol.accessor
const accessorString = dcol.accessor
dcol.accessor = row => _.get(row, accessorString)
return dcol
}
if (dcol.accessor && !dcol.id) {
console.warn(dcol)
throw new Error('A column id is required if using a non-string accessor for column above.')
}
if (!dcol.accessor) {
dcol.accessor = d => undefined
}
return dcol
},
getSorting (columns) {
return this.props.sorting || (this.state.sorting && this.state.sorting.length ? this.state.sorting : this.getInitSorting(columns))
},
getPagesLength () {
return this.props.manual ? this.props.pages
: Math.ceil(this.props.data.length / this.getStateOrProp('pageSize'))
},
getMinRows () {
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
},
// User actions
setPage (page) {
this.setState({
@@ -570,14 +585,13 @@ export default React.createClass({
this.fireOnChange()
})
},
setPageSize (pageSize) {
const currentPageSize = this.getStateOrProp('pageSize')
const currentPage = this.getPropOrState('page')
const currentRow = currentPageSize * currentPage
const page = Math.floor(currentRow / pageSize)
setPageSize (newPageSize) {
const { pageSize, page } = this.getResolvedState()
const currentRow = pageSize * page
const newPage = Math.floor(currentRow / pageSize)
this.setState({
pageSize,
page
pageSize: newPageSize,
page: newPage
}, () => {
this.fireOnChange()
})
+8 -8
View File
@@ -10,11 +10,11 @@ const defaultButton = (props) => (
export default React.createClass({
getInitialState () {
return {
page: this.props.currentPage
page: this.props.page
}
},
componentWillReceiveProps (nextProps) {
this.setState({page: nextProps.currentPage})
this.setState({page: nextProps.page})
},
getSafePage (page) {
return Math.min(Math.max(page, 0), this.props.pagesLength - 1)
@@ -27,11 +27,11 @@ export default React.createClass({
applyPage (e) {
e && e.preventDefault()
const page = this.state.page
this.changePage(page === '' ? this.props.currentPage : page)
this.changePage(page === '' ? this.props.page : page)
},
render () {
const {
currentPage,
page,
pagesLength,
showPageSizeOptions,
pageSizeOptions,
@@ -55,7 +55,7 @@ export default React.createClass({
<PreviousComponent
onClick={(e) => {
if (!canPrevious) return
this.changePage(currentPage - 1)
this.changePage(page - 1)
}}
disabled={!canPrevious}
>
@@ -82,8 +82,8 @@ export default React.createClass({
onBlur={this.applyPage}
/>
</form>
) : (
<span className='-currentPage'>{currentPage + 1}</span>
) : (
<span className='-currentPage'>{page + 1}</span>
)} {this.props.ofText} <span className='-totalPages'>{pagesLength}</span>
</span>
{showPageSizeOptions && (
@@ -109,7 +109,7 @@ export default React.createClass({
<NextComponent
onClick={(e) => {
if (!canNext) return
this.changePage(currentPage + 1)
this.changePage(page + 1)
}}
disabled={!canNext}
>
+16 -1
View File
@@ -1,3 +1,6 @@
import React from 'react'
import classnames from 'classnames'
//
export default {
get,
takeRight,
@@ -7,7 +10,8 @@ export default {
clone,
remove,
getFirstDefined,
sum
sum,
makeTemplateComponent
}
function remove (a, b) {
@@ -100,3 +104,14 @@ function sum (arr) {
return a + b
}, 0)
}
function makeTemplateComponent (compClass) {
return ({children, className, ...rest}) => (
<div
className={classnames(compClass, className)}
{...rest}
>
{children}
</div>
)
}
+1 -1
View File
@@ -85,7 +85,7 @@ const ServerSide = React.createClass({
}]
}]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
pageSize={10}
defaultPageSize={10}
data={this.state.data} // Set the rows to be displayed
pages={this.state.pages} // Display the total number of pages
loading={this.state.loading} // Display the loading overlay when we need it
+2 -2
View File
@@ -38,7 +38,7 @@ export default () => {
<ReactTable
data={data}
columns={columns}
pageSize={10}
defaultPageSize={10}
/>
</div>
<div style={{textAlign: 'center'}}>
@@ -78,7 +78,7 @@ return (
<ReactTable
data={data}
columns={columns}
pageSize={10}
defaultPageSize={10}
/>
)
`
+3 -3
View File
@@ -38,7 +38,7 @@ export default () => {
<ReactTable
data={data}
columns={columns}
pageSize={10}
defaultPageSize={10}
subComponent={(row) => {
return (
<div style={{padding: '20px'}}>
@@ -47,7 +47,7 @@ export default () => {
<ReactTable
data={data}
columns={columns}
pageSize={10}
defaultPageSize={10}
subComponent={(row) => {
return (
<div style={{padding: '20px'}}>
@@ -95,7 +95,7 @@ export default (
<ReactTable
data={data}
columns={columns}
pageSize={10}
defaultPageSize={10}
subComponent={(row) => {
return (
<code><pre>{JSON.stringify(row, null, 2)}</pre></code>