124 | );
125 | }
126 | }
127 |
128 | BugEdit.propTypes = {
129 | params: React.PropTypes.object.isRequired,
130 | };
131 |
132 |
--------------------------------------------------------------------------------
/src/BugList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import update from 'react-addons-update';
3 | import { Link } from 'react-router';
4 |
5 | import BugFilter from './BugFilter.jsx';
6 | import BugAdd from './BugAdd.jsx';
7 |
8 | /*
9 | * BugRow and BugTable are stateless, so they can be defined as pure functions
10 | * that only render. Both the following do the same, but with slightly different
11 | * styles.
12 | */
13 | const BugRow = (props) => (
14 |
46 | );
47 | }
48 |
49 | BugTable.propTypes = {
50 | bugs: React.PropTypes.array.isRequired,
51 | };
52 |
53 | export default class BugList extends React.Component {
54 | /*
55 | * In ES6, static members can only be functions. What we're doing here is to define
56 | * an accessor, so that contextTypes appears as a member variable to its callers.
57 | * It's anyway a const so we don't need a setter.
58 | */
59 | static get contextTypes() {
60 | return { router: React.PropTypes.object.isRequired };
61 | }
62 |
63 | static get propTypes() {
64 | return { location: React.PropTypes.object.isRequired };
65 | }
66 |
67 | constructor() {
68 | super();
69 | /*
70 | * Using ES6 way of intializing state
71 | */
72 | this.state = { bugs: [] };
73 | /*
74 | * React on ES6 has no auto-binding. We have to bind each class method. Doing it in
75 | * the constructor is the recommended way, since it is bound only once per instance.
76 | * No need to bind loadData() since that's never called from an event, only from other
77 | * methods which are already bound.
78 | */
79 | this.addBug = this.addBug.bind(this);
80 | this.changeFilter = this.changeFilter.bind(this);
81 | }
82 |
83 | componentDidMount() {
84 | console.log('BugList: componentDidMount');
85 | this.loadData();
86 | }
87 |
88 | componentDidUpdate(prevProps) {
89 | const oldQuery = prevProps.location.query;
90 | const newQuery = this.props.location.query;
91 | // todo: comparing shallow objects -- better way?
92 | // todo: when do we get called even when there's no change?
93 | if (oldQuery.priority === newQuery.priority &&
94 | oldQuery.status === newQuery.status) {
95 | console.log('BugList: componentDidUpdate, no change in filter, not updating');
96 | return;
97 | }
98 | console.log('BugList: componentDidUpdate, loading data with new filter');
99 | this.loadData();
100 | }
101 |
102 | loadData() {
103 | fetch(`/api/bugs/${this.props.location.search}`).then(response =>
104 | response.json()
105 | ).then(bugs => {
106 | this.setState({ bugs });
107 | }).catch(err => {
108 | console.log(err);
109 | // In a real app, we'd inform the user as well.
110 | });
111 | }
112 |
113 | changeFilter(newFilter) {
114 | /*
115 | * 1.x of react-router does not support context.router. We'll need to do it
116 | * this way if we're using an earlier version of the react-router:
117 | * this.props.history.push({search: '?' + $.param(newFilter)})
118 | */
119 |
120 | /*
121 | * jQuery.param would have done this in one line for us, but we don't want
122 | * to include the entire library for just this.
123 | */
124 | const search = Object.keys(newFilter)
125 | .filter(k => newFilter[k] !== '')
126 | .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(newFilter[k])}`)
127 | .join('&');
128 |
129 | this.context.router.push({ search: `?${search}` });
130 | }
131 |
132 | addBug(newBug) {
133 | console.log('Adding bug:', newBug);
134 |
135 | fetch('/api/bugs', {
136 | method: 'POST',
137 | headers: { 'Content-Type': 'application/json' },
138 | body: JSON.stringify(newBug),
139 |
140 | }).then(res => res.json()).then(bug => {
141 | /*
142 | * We should not modify the state directly, it's immutable. So, we make a copy.
143 | * A deep copy is not required, since we are not modifying any bug. We are
144 | * only appending to the array, but we can't do a 'push'. If we do that,
145 | * any method referring to the current state will get wrong data.
146 | * In essence, the current state should show the old list of bugs, but the
147 | * new state should show the new list.
148 | */
149 | // let modifiedBugs = this.state.bugs.concat(bug);
150 | /*
151 | * Earlier, we were supposed to use import react/addons, which is now deprecated
152 | * in favour of using import react-addons-{addon}, since this is more efficient
153 | * for bundlers such as browserify and webpack, even though the code mostly resides
154 | * within the react npm itself.
155 | */
156 | const modifiedBugs = update(this.state.bugs, { $push: [bug] });
157 | this.setState({ bugs: modifiedBugs });
158 |
159 | }).catch(err => {
160 | // ideally, show error to user also.
161 | console.log('Error adding bug:', err);
162 | });
163 | }
164 |
165 | render() {
166 | console.log('Rendering BugList, num items:', this.state.bugs.length);
167 | return (
168 |