Skip to content

Commit

Permalink
Add basic documentation and demo
Browse files Browse the repository at this point in the history
  • Loading branch information
stevesims committed May 3, 2018
1 parent b4f6557 commit 9b15def
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 34 deletions.
66 changes: 64 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# react-sort-data

A data sorting component that uses render props.

[![Travis][build-badge]][build]
[![npm package][npm-badge]][npm]
[![Coveralls][coveralls-badge]][coveralls]

A data sorting component that uses render props.

[build-badge]: https://img.shields.io/travis/user/repo/master.png?style=flat-square
[build]: https://travis-ci.org/user/repo

Expand All @@ -14,3 +14,65 @@ A data sorting component that uses render props.

[coveralls-badge]: https://img.shields.io/coveralls/user/repo/master.png?style=flat-square
[coveralls]: https://coveralls.io/github/user/repo

Data sorting by this component intended primarily for sorting arrays of objects using string values of given keys. Provide the component with an array of `data` objects, and an array of `field` definitions (with one indicated as being the `default`) and you'll get back a sorted `data` array. A field definition may also contain a `sort` function, which should take a `reversed` argument and return back a function that can be used with `Array.sort`.

The component uses the render-props pattern so as to allow for maximum flexibility and control when rendering out sorted data.

## Usage

```sh
npm install react-sort-data --save
```

```js
import Sortable from 'react-sort-data';

const MyComponent = () => {
const andersonCharacters = [
{ name: 'Captain Scarlet', series: 'Captain Scarlet and the Mysterons', year: '1967' },
{ name: 'Captain Black', series: 'Captain Scarlet and the Mysterons', year: '1967' },
{ name: 'Brains', series: 'Thunderbirds', year: '1965' },
{ name: 'Lady Penelope', series: 'Thunderbirds', year: '1965' },
{ name: 'Scott Tracy', series: 'Thunderbirds', year: '1965' },
{ name: 'Joe McLaine', series: 'Joe 90', year: '1968' }
];
const fields = [
{ key: 'name', default: true },
{ key: 'series' },
{ key: 'year', reversed: true }
];

return (
<Sortable data={andersonCharacters} fields={fields}>
{({ data, reversed, setSortField, sortField }) => (
<table>
<thead>
<tr>
{fields.map(({ key }) => {
const selected = (sortField === key);
return <th key={key} onClick={() => setSortField(key)}>
{selected && '> '}{key} {selected && (reversed ? '^' : 'v')}
</th>;
})}
</tr>
</thead>
<tbody>
{data.map(row => (
<tr key={row.name}>
<td>{row.name}</td>
<td>{row.series}</td>
<td>{row.year}</td>
</tr>
))}
</tbody>
</table>
)}
</Sortable>
);
};
```

## Contributors

- [Steve Sims](https://github.com/stevesims)
95 changes: 83 additions & 12 deletions demo/src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,86 @@
import React, {Component} from 'react'
import {render} from 'react-dom'
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render } from 'react-dom';

import Example from '../../src'
import Sortable from '../../src';

class Demo extends Component {
render() {
return <div>
<h1>react-sort-data Demo</h1>
<Example/>
</div>
}
}
const characters = [
{ name: 'Captain Scarlet', series: 'Captain Scarlet and the Mysterons', year: '1967' },
{ name: 'Captain Black', series: 'Captain Scarlet and the Mysterons', year: '1967' },
{ name: 'Brains', series: 'Thunderbirds', year: '1965' },
{ name: 'Lady Penelope', series: 'Thunderbirds', year: '1965' },
{ name: 'Scott Tracy', series: 'Thunderbirds', year: '1965' },
{ name: 'Joe McLaine', series: 'Joe 90', year: '1968' },
];

render(<Demo/>, document.querySelector('#demo'))
const TableExample = () => {
const fields = [
{ key: 'name', default: true },
{ key: 'series' },
{ key: 'year', reversed: true },
];

return (
<Sortable data={characters} fields={fields}>
{
({
data, reversed, setSortField, sortField,
}) => (
<table>
<thead>
<tr>
{fields.map(({ key }) => {
const selected = (sortField === key);
return (
<th key={key} onClick={() => setSortField(key)}>
{selected && '> '}{key} {selected && (reversed ? '^' : 'v')}
</th>
);
})}
</tr>
</thead>
<tbody>
{data.map(row => (
<tr key={row.name}>
<td>{row.name}</td>
<td>{row.series}</td>
<td>{row.year}</td>
</tr>
))}
</tbody>
</table>
)
}
</Sortable>
);
};

const ULExample = () => (
<Sortable
data={characters.map(character => character.name)}
defaultSort={reversed => (a, b) => (reversed ? -1 : 1) * a.localeCompare(b)}
render={({ data, setSortField }) => (
<div>
<ul>
{data.map(character => <li key={character}>{character}</li>)}
</ul>
<button onClick={() => setSortField()}>Reverse</button>
</div>
)}
/>
);

const Demo = () => (
<div>
<h1>react-sort-data Demo</h1>

<h2>Example using a Table-based display, rendering via children</h2>
<TableExample />

<h2>Example using UL-based display, an alternative sorting method, and render prop</h2>
<ULExample />
</div>
);

// eslint-disable-next-line no-undef
render(<Demo />, document.querySelector('#demo'));
35 changes: 17 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class Sortable extends Component {
key: PropTypes.string.isRequired,
reversed: PropTypes.bool,
sort: PropTypes.func,
})).isRequired,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
})),
data: PropTypes.arrayOf(PropTypes.any).isRequired,
defaultSort: PropTypes.func,
render: PropTypes.func,
}
Expand All @@ -19,28 +19,27 @@ class Sortable extends Component {
children: null,
defaultSort: (reverseSort, byField) =>
(a, b) => (reverseSort ? -1 : 1) * (a[byField] || '').localeCompare(b[byField] || ''),
fields: [],
render: null,
}

static getDerivedStateFromProps(nextProps, prevState) {
// find default field to sort on
if (Array.isArray(nextProps.fields)) {
if (
prevState.sortField
&& nextProps.fields.find(field => field.key === prevState.sortField)
) {
// old selected sortField is still valid, so don't change state
return null;
}
if (
prevState.sortField
&& nextProps.fields.find(field => field.key === prevState.sortField)
) {
// old selected sortField is still valid, so don't change state
return null;
}

const fieldInfo = nextProps.fields.find(field => field.default);
const fieldInfo = nextProps.fields.find(field => field.default);

if (fieldInfo) {
return {
sortField: fieldInfo.key,
reversed: !!fieldInfo.reversed,
};
}
if (fieldInfo) {
return {
sortField: fieldInfo.key,
reversed: !!fieldInfo.reversed,
};
}

return null;
Expand All @@ -64,7 +63,7 @@ class Sortable extends Component {
render() {
const { sortField, reversed } = this.state;
const {
children, fields = [], data, defaultSort, render,
children, fields, data, defaultSort, render,
} = this.props;
const { sort = defaultSort } = (fields.find(field => field.key === sortField) || {});
const sortedData = sort ? [...data].sort(sort(reversed, sortField)) : [...data];
Expand Down
4 changes: 2 additions & 2 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ describe('Component', () => {
});

it('will render with basic props', () => {
render(<Component fields={[]} data={[]} />, node, () => {
render(<Component data={[]} />, node, () => {
expect(node.innerHTML).toContain('');
});
});

it('can render via children', () => {
render(
<Component fields={[]} data={[]}>
<Component data={[]}>
{() => <div>Content</div>}
</Component>,
node,
Expand Down

0 comments on commit 9b15def

Please sign in to comment.