Lists

A common UI element in both mobile and desktop contexts is rendering lists of items. This is easy enough to do without the support of a CSS library, but libraries help keep the look and feel consistent. Let's implement a list that's controlled by a set of filters. First, you have the component that renders the react-bootstrap components:

import React from 'react';
import PropTypes from 'prop-types';

import {
Button,
ButtonGroup,
ListGroupItem,
ListGroup,
Glyphicon
} from 'react-bootstrap';

import './FilteredList.css';

// Utility function to get the bootstrap style
// for an item, based on the "done" value.
const itemStyle = done => (done ? { bsStyle: 'success' } : {});

// Utility component for rendering a bootstrap
// icon based on the value of "done".
const ItemIcon = ({ done }) =>
done ? <Glyphicon glyph="ok" className="item-done" /> : null;

// Renders a list of items, and a set of filter
// controls to change what's displayed in the
// list.
const FilteredList = props => (
<section>
{/* Three buttons that control what's displayed
in the list below. Clicking one of these
buttons will toggle the state of the others. */}
<ButtonGroup className="filters">
<Button active={props.todoFilter} onClick={props.todoClick}>
Todo
</Button>
<Button active={props.doneFilter} onClick={props.doneClick}>
Done
</Button>
<Button active={props.allFilter} onClick={props.allClick}>
All
</Button>
</ButtonGroup>

{/* Renders the list of items. It passes the
"props.filter()" function to "items.filter()".
When the buttons above are clicked, the "filter"
function is changed. */}
<ListGroup>
{props.items.filter(props.filter).map(i => (
<ListGroupItem
key={i.name}
onClick={props.itemClick(i)}
href="#"
{...itemStyle(i.done)}
>
{i.name}
<ItemIcon done={i.done} />
</ListGroupItem>
))}
</ListGroup>
</section>
);

FilteredList.propTypes = {
todoFilter: PropTypes.bool.isRequired,
doneFilter: PropTypes.bool.isRequired,
allFilter: PropTypes.bool.isRequired,
todoClick: PropTypes.func.isRequired,
doneClick: PropTypes.func.isRequired,
allClick: PropTypes.func.isRequired,
itemClick: PropTypes.func.isRequired,
filter: PropTypes.func.isRequired,
items: PropTypes.array.isRequired
};

export default FilteredList;

First, you have the <ButtonGroup> and the <Button> elements. These are the filters that the user can apply to the list. By default, only todo items are displayed. But, they can choose to filter by done items, or to show all items.

The list itself is a <ListGroup> element with <ListGroupItem> elements as children. The item renders differently, depending on the done state of the item. The end result looks like this:

You can toggle the done state of a list item simply by clicking on the Done button. What's nice about the way this component works is that if you're viewing todo items and mark one as done, it's taken off the list because it no longer meets the current filter criteria. The filter is re-evaluated because the component is re-rendered. Here's what an item that's marked as done looks like:

Now let's take a look at the container component that handles the state of the filter buttons and the item list:

import React, { Component } from 'react';
import { fromJS } from 'immutable';

import FilteredList from './FilteredList';

class FilteredListContainer extends Component {
// Controls the state of the the filter buttons
// as well as the state of the function that
// filters the item list.
state = {
data: fromJS({
// The items...
items: [
{ name: 'First item', done: false },
{ name: 'Second item', done: false },
{ name: 'Third item', done: false }
],

// The filter button states...
todoFilter: true,
doneFilter: false,
allFilter: false,

// The default filter...
filter: i => !i.done,

// The "todo" filter button was clicked.
todoClick: () => {
this.data = this.data.merge({
todoFilter: true,
doneFilter: false,
allFilter: false,
filter: i => !i.done
});
},

// The "done" filter button was clicked.
doneClick: () => {
this.data = this.data.merge({
todoFilter: false,
doneFilter: true,
allFilter: false,
filter: i => i.done
});
},

// The "all" filter button was clicked.
allClick: () => {
this.data = this.data.merge({
todoFilter: false,
doneFilter: false,
allFilter: true,
filter: () => true
});
},

// When the item is clicked, toggle it's
// "done" state.
itemClick: item => e => {
e.preventDefault();

this.data = this.data.update('items', items =>
items.update(
items.findIndex(i => i.get('name') === item.name),
i => i.update('done', done => !done)
)
);
}
})
};

// Getter for "Immutable.js" state data...
get data() {
return this.state.data;
}

// Setter for "Immutable.js" state data...
set data(data) {
this.setState({ data });
}

render() {
return <FilteredList {...this.state.data.toJS()} />;
}
}

export default FilteredListContainer;

This component has four pieces of state and four event handler functions. Three pieces of state do nothing more than track which filter button is selected. The filter state is the callback function that's used by <FilteredList> to filter the items. The tactic is to pass a different filter function to the child view, based on the filter selection.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.102.124