{"action":"created","issue":{"url":"https://api.github.com/repos/gcanti/tcomb-form/issues/252","labels_url":"https://api.github.com/repos/gcanti/tcomb-form/issues/252/labels{/name}","comments_url":"https://api.github.com/repos/gcanti/tcomb-form/issues/252/comments","events_url":"https://api.github.com/repos/gcanti/tcomb-form/issues/252/events","html_url":"https://github.com/gcanti/tcomb-form/issues/252","id":117038636,"number":252,"title":"Tags input","user":{"login":"Xananax","id":836082,"avatar_url":"https://avatars.githubusercontent.com/u/836082?v=3","gravatar_id":"","url":"https://api.github.com/users/Xananax","html_url":"https://github.com/Xananax","followers_url":"https://api.github.com/users/Xananax/followers","following_url":"https://api.github.com/users/Xananax/following{/other_user}","gists_url":"https://api.github.com/users/Xananax/gists{/gist_id}","starred_url":"https://api.github.com/users/Xananax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Xananax/subscriptions","organizations_url":"https://api.github.com/users/Xananax/orgs","repos_url":"https://api.github.com/users/Xananax/repos","events_url":"https://api.github.com/users/Xananax/events{/privacy}","received_events_url":"https://api.github.com/users/Xananax/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/gcanti/tcomb-form/labels/factories","name":"factories","color":"5319e7"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-11-16T02:39:10Z","updated_at":"2015-11-16T08:48:29Z","closed_at":null,"body":"Since I had a bit of a hard time wrapping my head around custom factories, here's my tags input component, in the hopes it would be useful to someone else:\r\n\r\n```js\r\n//TagsComponent.js\r\nimport React,{Component,PropTypes} from 'react';\r\nimport t from 'tcomb-form';\r\nimport cx from 'classnames';\r\n\r\n\r\nconst onTagClick = (id,onClick) => (evt) => {\r\n\tevt && evt.preventDefault()\r\n\tonClick(id);\r\n}\r\n\r\nfunction Tag({tag,id,onClick}){\r\n\treturn (<div>{tag} <span onClick={onTagClick(id,onClick)}>x</span></div>)\r\n}\r\n\r\nfunction onChange(sep,evt){\r\n\tconst currentValue = evt.target.value\r\n\tif(currentValue && sep.test(currentValue)){\r\n\t\tconst addedValue = currentValue.trim();\r\n\t\tif(this.state.value && this.state.value.indexOf(addedValue)>=0){\r\n\t\t\treturn this.setState({currentValue:''});\r\n\t\t}\r\n\t\tconst value = (this.state.value ? [...this.state.value,addedValue] : [addedValue]);\r\n\t\treturn this.setState(\r\n\t\t\t{value,currentValue:''}\r\n\t\t,\t() => this.props.onChange(value, this.props.ctx.path)\r\n\t\t);\r\n\t}\r\n\tthis.setState({currentValue});\r\n}\r\n\r\nfunction onRemoveTag(id){\r\n\tconst {value} = this.state;\r\n\tthis.setState({value:value.filter((tag,i)=>i!==id)});\r\n}\r\n\r\nclass TagsComponent extends Component{\r\n\tstatic propTypes = {\r\n\t\tseparator:PropTypes.instanceOf(RegExp)\r\n\t,\tvalue:PropTypes.array\r\n\t,\tcurrentValue:PropTypes.string\r\n\t,\tcomponent:PropTypes.element\r\n\t}\r\n\tstatic defaultProps = {\r\n\t\tseparator:/\\s$/\r\n\t,\tvalue:[]\r\n\t,\tcurrentValue:''\r\n\t}\r\n\tconstructor(props,context) {\r\n\t\tsuper(props,context)\r\n\t\tthis.state = {\r\n\t\t\thasError: false\r\n\t\t,\tvalue:props.value\r\n\t\t,\tcurrentValue:props.currentValue\r\n\t\t};\r\n\t\tthis.onChange = onChange.bind(this,(props.separator));\r\n\t\tthis.onRemoveTag = onRemoveTag.bind(this);\r\n\t}\r\n\tshouldComponentUpdate(nextProps, nextState) {\r\n\t\treturn (\r\n\t\t\tnextState.value !== this.state.value ||\r\n\t\t\tnextState.hasError !== this.state.hasError ||\r\n\t\t\tnextProps.value !== this.props.value ||\r\n\t\t\tnextProps.options !== this.props.options ||\r\n\t\t\tnextProps.onChange !== this.props.onChange ||\r\n\t\t\tnextProps.type !== this.props.type ||\r\n\t\t\tnextState.currentValue !== this.state.currentValue\r\n\t\t);\r\n\t}\r\n\tgetValidationOptions(){\r\n\t\treturn {\r\n\t\t\tpath: this.props.ctx.path,\r\n\t\t\tcontext: t.mixin(t.mixin({}, this.props.context || this.props.ctx.context), { options: this.props.options })\r\n\t\t};\r\n\t}\r\n\tvalidate() {\r\n\t\tconst {value} = this.state;\r\n\t\tconst result = t.validate(value, this.props.type, this.getValidationOptions());\r\n\t\tthis.setState({hasError: !result.isValid()});\r\n\t\treturn result;\r\n\t}\r\n\trenderTags(value){\r\n\t\treturn (value && value.map((tag,id)=>\r\n\t\t\t<Tag tag={tag} id={id} key={id} onClick={this.onRemoveTag}/>\r\n\t\t));\r\n\t}\r\n\trender(){\r\n\t\tvar options = this.props.options || {};\r\n\t\tconst {value,currentValue,hasError} = this.state;\r\n\t\tconst {ctx} = this.props;\r\n\t\tconst CustomComponent = this.props.component;\r\n\t\tconst label = options.label || (ctx.auto === 'labels' && ctx.label);\r\n\t\tconst placeholder = ((!label && ctx.auto !== 'none')?\r\n\t\t\t!t.Nil.is(options.placeholder) ? options.placeholder : ctx.label :\r\n\t\t\tnull\r\n\t\t);\r\n\t\tconst name = options.name || ctx.name;\r\n\t\tconst error = t.Func.is(options.error) ? options.error(this.state.value) : options.error;\r\n\r\n\t\tvar formGroupClasses = cx({\r\n\t\t\t'form-group': true\r\n\t\t,\t'has-feedback': true\r\n\t\t,\t'has-error': hasError\r\n\t\t});\r\n\r\n\t\tconst inputProps = {\r\n\t\t\tdisabled:options.disabled\r\n\t\t,\tclassName:'form-control'\r\n\t\t,\tname\r\n\t\t,\tplaceholder\r\n\t\t,\tonChange:this.onChange\r\n\t\t,\ttype:'text'\r\n\t\t,\tvalue:currentValue\r\n\t\t}\r\n\r\n\t\tconst input = CustomComponent ? (<CustomComponent {...inputProps}/>) : (<input {...inputProps}/>);\r\n\r\n\t\treturn (<div className={formGroupClasses}>\r\n\t\t\t{label ? (<label className=\"control-label\">{label}</label>) : null}\r\n\t\t\t{this.renderTags(value)}\r\n\t\t\t{input}\r\n\t\t\t<span className=\"glyphicon glyphicon-search form-control-feedback\"></span>\r\n\t\t\t{(error ? (<span className=\"help-block error-block\">{error}</span>): null)}\r\n\t\t\t{(options.help ?(<span className=\"help-block\">{options.help}</span>) : null)}\r\n\t\t</div>);\r\n\t}\r\n};\r\n\r\nexport default TagsComponent;\r\n```\r\n\r\nUse it like this:\r\n```js\r\n//otherFile.js\r\nimport TagsComponent from './TagsComponent';\r\n\r\nvar Type = t.struct({\r\n  tags: t.list(t.String)\r\n});\r\n\r\nconst options = {\r\n      fields:{\r\n\t\ttags:{\r\n\t\t\tfactory:TagsComponent\r\n\t\t}\r\n\t}\r\n}\r\n\r\n<Form type={Type} options={options} />\r\n\r\n```\r\n\r\nHope it helps anyone, comments would be welcome."},"comment":{"url":"https://api.github.com/repos/gcanti/tcomb-form/issues/comments/156957630","html_url":"https://github.com/gcanti/tcomb-form/issues/252#issuecomment-156957630","issue_url":"https://api.github.com/repos/gcanti/tcomb-form/issues/252","id":156957630,"user":{"login":"gcanti","id":878912,"avatar_url":"https://avatars.githubusercontent.com/u/878912?v=3","gravatar_id":"","url":"https://api.github.com/users/gcanti","html_url":"https://github.com/gcanti","followers_url":"https://api.github.com/users/gcanti/followers","following_url":"https://api.github.com/users/gcanti/following{/other_user}","gists_url":"https://api.github.com/users/gcanti/gists{/gist_id}","starred_url":"https://api.github.com/users/gcanti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gcanti/subscriptions","organizations_url":"https://api.github.com/users/gcanti/orgs","repos_url":"https://api.github.com/users/gcanti/repos","events_url":"https://api.github.com/users/gcanti/events{/privacy}","received_events_url":"https://api.github.com/users/gcanti/received_events","type":"User","site_admin":false},"created_at":"2015-11-16T08:48:29Z","updated_at":"2015-11-16T08:48:29Z","body":"Hi @Xananax, thanks for sharing.\r\n\r\nAnother option would be using a third party tagsinput (or build your own) and then a custom factory:\r\n\r\n```js\r\nimport React from 'react';\r\nimport t from 'tcomb-form';\r\nimport TagsInput from 'react-tagsinput'; // I'm using this but you can build your own (and reusable!) tagsinput\r\n\r\n// import react-tagsinput.css...\r\n\r\nclass TagsComponent extends t.form.Component { // extend the base class\r\n\r\n  getTemplate() {\r\n    return (locals) => {\r\n      return (\r\n        <TagsInput value={locals.value} onChange={locals.onChange} />\r\n      );\r\n    };\r\n  }\r\n\r\n}\r\n\r\nexport default TagsComponent;\r\n```\r\n"}}