Skip to content

Commit 37f99dd

Browse files
author
jfusco
committed
Refactor component to be stateless
Refactor the Tags component to be stateless and allow the state of tags to be changed/controlled at the host component level. Keep all other business logic in the component layer. Update README to reflect new refactor of the component. Update options and events, remove onChange event. Update tags tests to use stateless compoment. Create an App fixture to be able to capture events and state change in the component so we can test properly. Update webpack to extract the css in a production build as well as a development build. Remove css-loader rule from rules config. Update example/docs to reflect new changes to the component.
1 parent 3ad2a00 commit 37f99dd

File tree

10 files changed

+440
-367
lines changed

10 files changed

+440
-367
lines changed

README.md

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,34 @@ import { render } from 'react-dom';
3131
import Tags from 'react-tagging-input';
3232

3333
class Application extends Component{
34+
state = {
35+
tags: ['foo', 'bar']
36+
};
37+
3438
constructor(props){
3539
super(props);
40+
}
3641

37-
this.state = {
38-
tags: ['hello', 'world']
39-
}
42+
onTagAdded(tag) {
43+
this.setState({
44+
tags: [...this.state.tags, tag]
45+
});
4046
}
4147

42-
onTagsChange(tags){
43-
console.log(`new tags: ${tags}`);
48+
onTagRemoved(tag, index) {
49+
this.setState({
50+
tags: this.state.tags.filter((tag, i) => i !== index)
51+
});
4452
}
4553

4654
render(){
4755
return (
4856
<div>
4957
<Tags
50-
initialTags={this.state.tags}
51-
placeholder="Add a tag"
52-
onChange={this.onTagsChange.bind(this)} />
58+
tags={this.state.tags}
59+
placeholder="Add a tag"
60+
onAdded={this.onTagAdded.bind(this)}
61+
onRemoved={this.onTagRemoved.bind(this)} />
5362
</div>
5463
);
5564
}
@@ -60,10 +69,9 @@ render(<Application />, document.getElementById('application'));
6069

6170
<a name="options"></a>
6271
#### Options
63-
* **[`initialTags`](#initialTags)**
72+
* **[`tags`](#tags)**
6473
* **[`placeholder`](#placeholder)**
6574
* **[`addKeys`](#addKeys)**
66-
* **[`onChange`](#onChange)**
6775
* **[`onAdded`](#onAdded)**
6876
* **[`onRemoved`](#onRemoved)**
6977
* **[`maxTags`](#maxTags)**
@@ -72,13 +80,15 @@ render(<Application />, document.getElementById('application'));
7280
* **[`uniqueTags`](#uniqueTags)**
7381
* **[`id`](#id)**
7482

75-
<a name="initialTags"></a>
76-
##### initialTags ~ optional ~ default `[]`
83+
<a name="tags"></a>
84+
##### tags ~ required
7785
An `array` of tags to be passed in and rendered right away in the component
7886
```js
79-
const tags = ['hello', 'world'];
87+
state = {
88+
tags: ['foo', 'bar']
89+
};
8090

81-
<Tags initialTags={tags} />
91+
<Tags tags={this.state.tags} />
8292
```
8393

8494
<a name="placeholder"></a>
@@ -97,17 +107,6 @@ An `array` of keyCodes used to tell the tags component which delimiter to use to
97107
<Tags addKeys={[13, 9, 32, 188]} />
98108
```
99109

100-
<a name="onChange"></a>
101-
##### onChange ~ optional
102-
A `function` fired anytime there is a change - returns the new `array` of tags
103-
```js
104-
onTagsChange(tags){
105-
console.log(`new tags: ${tags}`);
106-
}
107-
108-
<Tags onChange={this.onTagsChange} />
109-
```
110-
111110
<a name="onAdded"></a>
112111
##### onAdded ~ optional
113112
A `function` fired when a new tag is added - returns a `string` of the new tag
@@ -123,11 +122,11 @@ onTagAdded(tag){
123122
##### onRemoved ~ optional
124123
A `function` fired when a new tag is deleted - returns a `string` of the tag that was deleted
125124
```js
126-
onTagRemoved(tag){
127-
console.log(`deleted tag: ${tag}`);
125+
onTagRemoved(tag, index){
126+
console.log(`deleted tag: ${tag} at index ${index}`);
128127
}
129128

130-
<Tags onRemoved={this.onTagRemoved} />
129+
<Tags onRemoved={this.onTagRemoved.bind(this)} />
131130
```
132131

133132
<a name="maxTags"></a>

__tests__/Tags-test.js

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import { findDOMNode } from 'react-dom';
55
import { createRenderer, Simulate, renderIntoDocument } from 'react-addons-test-utils';
66
import Tags from '../src/component/js/Tags';
7+
import App from './__fixtures__/App';
78

89
const TEST_TAGS = [
910
'foo',
@@ -21,7 +22,7 @@ describe('Tags', () => {
2122
<Tags
2223
id="test-tags"
2324
placeholder="Custom placeholder text"
24-
initialTags={TEST_TAGS} />
25+
tags={TEST_TAGS} />
2526
);
2627

2728
tags = renderer.getRenderOutput();
@@ -58,7 +59,7 @@ describe('Tags - "initialTags"', () => {
5859
const renderList = () => {
5960
renderer.render(
6061
<Tags
61-
initialTags={TEST_TAGS} />
62+
tags={TEST_TAGS} />
6263
);
6364

6465
const list = renderer.getRenderOutput();
@@ -72,8 +73,9 @@ describe('Tags - "initialTags"', () => {
7273

7374
it('should render tags (DOM render)', () => {
7475
const tags = renderIntoDocument(
75-
<Tags
76-
initialTags={TEST_TAGS} />
76+
<div>
77+
<Tags tags={TEST_TAGS} />
78+
</div>
7779
);
7880

7981
const renderedDOM = findDOMNode(tags);
@@ -91,7 +93,7 @@ describe('Tags - "readOnly"', () => {
9193

9294
renderer.render(
9395
<Tags
94-
initialTags={TEST_TAGS}
96+
tags={TEST_TAGS}
9597
readOnly={true} />
9698
);
9799
});
@@ -113,20 +115,20 @@ describe('Tags - "readOnly"', () => {
113115
});
114116
});
115117

116-
117118
describe('Tags - "addKeys"', () => {
118119
let tags,
119120
input,
120121
tagContainer;
121122

122123
beforeEach(() => {
123124
tags = renderIntoDocument(
124-
<Tags
125-
initialTags={TEST_TAGS}
125+
<App
126+
tags={TEST_TAGS}
126127
addKeys={[13, 9, 32, 188]} />
127128
);
128129

129130
const renderedDOM = findDOMNode(tags);
131+
130132
tagContainer = renderedDOM.querySelector('.react-tags__container');
131133
input = renderedDOM.getElementsByTagName('input')[0];
132134

@@ -181,20 +183,21 @@ describe('Tags - events', () => {
181183

182184
const onTagAdded = jest.genMockFunction();
183185
const onTagRemoved = jest.genMockFunction();
184-
const onTagsChanged = jest.genMockFunction();
185186
const onTagsInputChange = jest.genMockFunction();
186187

187188
beforeEach(() => {
188189
tags = renderIntoDocument(
189-
<Tags
190-
initialTags={TEST_TAGS}
191-
onAdded={onTagAdded}
192-
onRemoved={onTagRemoved}
193-
onChange={onTagsChanged}
194-
onInputChange={onTagsInputChange} />
190+
<div>
191+
<Tags
192+
tags={TEST_TAGS}
193+
onAdded={onTagAdded}
194+
onRemoved={onTagRemoved}
195+
onInputChange={onTagsInputChange} />
196+
</div>
195197
);
196198

197199
const renderedDOM = findDOMNode(tags);
200+
198201
tagContainer = renderedDOM.querySelector('.react-tags__container');
199202
input = renderedDOM.getElementsByTagName('input')[0];
200203
});
@@ -206,38 +209,24 @@ describe('Tags - events', () => {
206209
});
207210

208211
describe('when adding a tag', () => {
209-
beforeEach(() => {
212+
it('should call the "onAdded" event and return the new tag', () => {
210213
input.value = TEST_TAGS[0];
211214

212215
Simulate.change(input);
213216
Simulate.keyDown(input, {key: 'Enter', keyCode: 13, which: 13});
214-
});
215217

216-
it('should call the "onAdded" event and return the new tag', () => {
217218
expect(onTagAdded).toBeCalledWith(TEST_TAGS[0]);
218219
});
219-
220-
it('should call the "onChange" event and return the new tags list as an array', () => {
221-
const newArray = TEST_TAGS.concat('foo');
222-
223-
expect(onTagsChanged).toBeCalledWith(newArray);
224-
});
225220
});
226221

227222
describe('when removing a tag', () => {
228-
beforeEach(() => {
223+
it('should call the "onRemoved" event and return the tag that was removed', () => {
229224
input.value = '';
230225

231226
Simulate.change(input);
232227
Simulate.keyDown(input, {key: 'Delete', keyCode: 8, which: 8});
233-
});
234-
235-
it('should call the "onRemoved" event and return the tag that was removed', () => {
236-
expect(onTagRemoved).toBeCalledWith(TEST_TAGS[1]);
237-
});
238228

239-
it('should call the "onChange" event and return the new tags list as an array', () => {
240-
expect(onTagsChanged).toBeCalledWith([TEST_TAGS[0]]);
229+
expect(onTagRemoved).toBeCalledWith(TEST_TAGS[1], 1);
241230
});
242231
});
243232

@@ -261,8 +250,8 @@ describe('Tags - removing', () => {
261250

262251
beforeEach(() => {
263252
tags = renderIntoDocument(
264-
<Tags
265-
initialTags={TEST_TAGS} />
253+
<App
254+
tags={TEST_TAGS} />
266255
);
267256

268257
const renderedDOM = findDOMNode(tags);
@@ -315,8 +304,8 @@ describe('Tags - removing', () => {
315304
describe('Tags - "uniqueTags"', () => {
316305
it('should allow duplicate tags to be created', () => {
317306
const tags = renderIntoDocument(
318-
<Tags
319-
initialTags={TEST_TAGS}
307+
<App
308+
tags={TEST_TAGS}
320309
uniqueTags={false} />
321310
);
322311

@@ -336,8 +325,8 @@ describe('Tags - "uniqueTags"', () => {
336325

337326
it('should not allow duplicate tags to be created', () => {
338327
const tags = renderIntoDocument(
339-
<Tags
340-
initialTags={TEST_TAGS}
328+
<App
329+
tags={TEST_TAGS}
341330
uniqueTags={true} />
342331
);
343332

@@ -358,8 +347,8 @@ describe('Tags - "maxTags"', () => {
358347
describe('when maxTags is set to 3', () => {
359348
it('should allow no more than 3 tags to be added', () => {
360349
const tags = renderIntoDocument(
361-
<Tags
362-
initialTags={TEST_TAGS}
350+
<App
351+
tags={TEST_TAGS}
363352
maxTags={3} />
364353
);
365354

@@ -385,8 +374,9 @@ describe('Tags - "deleteOnKeyPress"', () => {
385374
describe('when deleteOnKeyPress is false', () => {
386375
it('should not remove tags on backspace', () => {
387376
const tags = renderIntoDocument(
388-
<Tags
389-
initialTags={TEST_TAGS}
377+
<App
378+
tags={TEST_TAGS}
379+
addKeys={[13, 9, 32, 188]}
390380
deleteOnKeyPress={false} />
391381
);
392382

__tests__/__fixtures__/App.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
import React, { Component } from 'react';
4+
import { render } from 'react-dom';
5+
import Tags from '../../src/component/js/Tags';
6+
7+
class App extends Component {
8+
state = {
9+
tags: this.props.tags
10+
};
11+
12+
constructor(props){
13+
super(props);
14+
}
15+
16+
onTagAdded(tag) {
17+
this.setState({
18+
tags: [...this.state.tags, tag]
19+
});
20+
}
21+
22+
onTagRemoved(tag, index) {
23+
this.setState({
24+
tags: this.state.tags.filter((tag, i) => i !== index)
25+
});
26+
}
27+
28+
render () {
29+
return (
30+
<div>
31+
<Tags
32+
{...this.props}
33+
{...this.state}
34+
onAdded={::this.onTagAdded}
35+
onRemoved={::this.onTagRemoved} />
36+
</div>
37+
);
38+
}
39+
}
40+
41+
export default App;

0 commit comments

Comments
 (0)