Closed BigPrimeNumbers closed 8 years ago
Hi,
Yes it's possible. See here for examples of integrating custom components:
https://github.com/gcanti/tcomb-form/issues/261 https://github.com/gcanti/tcomb-form/issues/273 https://github.com/gcanti/tcomb-form/issues/274
Thanks for the references. Unfortunately, I'm a bit new at this and am still a bit confused. I've got the following in a file called AutocompleteFactory.js:
import React from 'react'
import t from 'tcomb-form'
import Autosuggest from 'react-autosuggest'
class AutosuggestComponent extends t.form.Component { // extend the base class
getTemplate() {
return (locals) => {
return (
<Autosuggest
suggestions={locals.suggestions}
onSuggestionsUpdateRequested={locals.onSuggestionsUpdateRequested}
getSuggestionValue={locals.getSuggestionValue}
renderSuggestion={locals.renderSuggestion}
inputProps={locals.inputProps}
/>
)
}
}
}
export default AutosuggestComponent
and then in the main file:
import React from 'react'
import { Link } from 'react-router'
import Autosuggest from 'react-autosuggest'
import t from 'tcomb-form'
import AutosuggestComponent from './AutosuggestFactory'
const Form = t.form.Form;
const Type = t.struct({
name: t.String,
company: t.maybe(t.String)
});
const options = {
fields: {
name: {
factory: AutosuggestComponent
},
company: {
factory: AutosuggestComponent
}
}
};
...
export default class foo extends React.Component {
...
onSubmit(evt) {
evt.preventDefault()
const value = this.refs.form.getValue()
if (value) {
console.log(value)
}
}
onDrugSuggestionsUpdateRequested({value, reason}) {
this.setState({
suggestions: getSuggestions(value)
});
}
render() {
let inputProps = {
suggestions: suggestions,
onSuggestionsUpdateRequested: this.onSuggestionsUpdateRequested,
getSuggestionValue: getSuggestionValue,
renderSuggestion: renderSuggestion,
inputProps: inputCompanyProps
}
return (
<div>
<Form
ref="form"
type={Type}
options={options}
value={inputProps}
/>
<button onClick={this.onSubmit}>Save</button>
</div>
)
}
But the form just renders with ordinary text fields, not with Autocomplete components. Any help is geatly appreciated. Thanks for your time and hard work!
Hi,
With your actual code, I can help you more.
I think that a custom template should work:
import Autosuggest from 'react-autosuggest'
//
// a suggestion config
//
const languages = [
{
name: 'C',
year: 1972
},
{
name: 'Elm',
year: 2012
},
{
name: 'Javascript',
year: 1995
},
{
name: 'Python',
year: 1991
}
]
function getSuggestions(value) {
return languages.filter(language => language.name.toLowerCase().indexOf(value) === 0)
}
function getSuggestionValue(suggestion) {
return suggestion.name
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
)
}
//
// Template
// given a suggestion config returns the proper template
//
function getTemplate(options) {
function renderInput(locals) {
const value = locals.value || '' // react-autosuggest doesn't like null or undefined as value
const inputProps = {
...locals.attrs,
value: value,
onChange: (evt, { newValue }) => {
locals.onChange(newValue)
}
}
const suggestions = options.getSuggestions(value)
return (
<Autosuggest
suggestions={suggestions}
getSuggestionValue={options.getSuggestionValue}
renderSuggestion={options.renderSuggestion}
inputProps={inputProps}
/>
)
}
return t.form.Form.templates.textbox.clone({ renderInput })
}
//
// Usage
//
const Type = t.struct({
language: t.String
})
const options = {
fields: {
language: {
template: getTemplate({
getSuggestions,
getSuggestionValue,
renderSuggestion
})
}
}
}
Thanks so much for your help with this. A few questions:
//AutosuggestFactory.js
import React from 'react'
import t from 'tcomb-form'
import Autosuggest from 'react-autosuggest'
class AutosuggestComponent extends t.form.Component { // extend the base class
getTemplate(options) {
function renderInput(locals) {
const value = locals.value || '' // react-autosuggest doesn't like null or undefined as value
const inputProps = {
...locals.attrs,
value: value,
onChange: (evt, { newValue }) => {
locals.onChange(newValue)
}
}
const suggestions = options.getSuggestions(value)
return (
<Autosuggest
suggestions={suggestions}
getSuggestionValue={options.getSuggestionValue}
//onSuggestionsUpdateRequested={options. onSuggestionsUpdateRequested}
renderSuggestion={options.renderSuggestion}
inputProps={inputProps}
/>
)
}
return t.form.Form.templates.textbox.clone({ renderInput })
}
}
export default AutosuggestComponent
and in the main app:
//app.js
import React from 'react'
import { Link } from 'react-router'
import Autosuggest from 'react-autosuggest'
import t from 'tcomb-form'
import AutosuggestComponent from './AutosuggestFactory'
const Form = t.form.Form;
const Type = t.struct({
name: t.String,
company: t.maybe(t.String)
});
const options = {
fields: {
name: {
factory: AutosuggestComponent.getTemplate({
getSuggestions,
getSuggestionValue,
renderSuggestion,
})
}
}
}
...
// THE REST IS THE SAME CODE FROM ABOVE
and get the error: "Uncaught TypeError: _AutosuggestFactory2.default.getTemplate is not a function"
function create() {
var overrides = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
function textbox(locals) {
locals.config = textbox.getConfig(locals); // THE ERROR IS HERE
locals.attrs = textbox.getAttrs(locals);
if (locals.type === 'hidden') {
return textbox.renderHiddenTextbox(locals);
}
var children = locals.config.horizontal ? textbox.renderHorizontal(locals) : textbox.renderVertical(locals);
return textbox.renderFormGroup(children, locals);
}
...
Here is a code reference to a simple implementation of a single Autocomplete if it helps Codepen Autocomplete example
How could I separate the factory into a separate file?
You don't need a factory nor a onSuggestionsUpdateRequested
implementation, just a template:
Complete working example
import Autosuggest from 'react-autosuggest'
const languages = [
{
name: 'C',
year: 1972
},
{
name: 'Elm',
year: 2012
},
{
name: 'Javascript',
year: 1995
},
{
name: 'Python',
year: 1991
}
]
function getSuggestions(value) {
return languages.filter(language => language.name.indexOf(value) === 0)
}
function getSuggestionValue(suggestion) {
return suggestion.name
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
)
}
// define the template only once
function getTemplate(options) {
function renderInput(locals) {
const value = locals.value || '' // react-autosuggest doesn't like null or undefined as value
const inputProps = {
...locals.attrs,
value: value,
onChange: (evt, { newValue }) => {
locals.onChange(newValue)
}
}
const suggestions = options.getSuggestions(value)
return (
<Autosuggest
suggestions={suggestions}
getSuggestionValue={options.getSuggestionValue}
renderSuggestion={options.renderSuggestion}
inputProps={inputProps}
/>
)
}
return t.form.Form.templates.textbox.clone({ renderInput })
}
// define the type
const Type = t.struct({
language: t.String
})
const options = {
fields: {
language: {
attrs: {
placeholder: 'Type C'
},
template: getTemplate({
getSuggestions,
getSuggestionValue,
renderSuggestion
})
}
}
}
const App = React.createClass({
getInitialState() {
return {
value: {},
}
},
onSubmit(evt) {
evt.preventDefault()
const v = this.refs.form.getValue()
if (v) {
console.log(v) // eslint-disable-line
}
},
onChange(value, path) {
this.setState({ value })
},
render() {
return (
<form onSubmit={this.onSubmit}>
<t.form.Form
ref="form"
type={Type}
options={options}
value={this.state.value}
onChange={this.onChange}
/>
<div className="form-group">
<button type="submit" className="btn btn-primary">Save</button>
</div>
</form>
)
}
})
Hi @gcanti I tried this but when I press the arrow keys to select a suggestion the vale of the input change and the suggestions too so I can only select the first or the last suggestion.
I guess you could disentangle the current textbox value and the current selection, something like:
import Autosuggest from 'react-autosuggest'
const languages = [
{
name: 'C',
year: 1972
},
{
name: 'Elm',
year: 2012
},
{
name: 'Elm2',
year: 2012
},
{
name: 'Javascript',
year: 1995
},
{
name: 'Python',
year: 1991
}
]
function getSuggestions(value) {
return languages.filter(language => language.name.indexOf(value) === 0)
}
function getSuggestionValue(suggestion) {
return suggestion.name
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
)
}
class Auto extends React.Component {
consructor(props) {
super(props)
this.state = {}
}
render() {
const value = this.props.value || '' // react-autosuggest doesn't like null or undefined as value
const onChange = (evt) => {
if (evt.reason === 'enter' || evt.reason === 'click') {
this.props.onChange(this.state.value)
}
if (evt.reason === 'type') {
this.props.onChange(evt.value)
}
}
const inputProps = {
...this.props.attrs,
value: value,
onChange: (evt, { newValue }) => {
this.setState({ value: newValue })
}
}
const suggestions = this.props.options.getSuggestions(value)
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsUpdateRequested={onChange}
getSuggestionValue={this.props.options.getSuggestionValue}
renderSuggestion={this.props.options.renderSuggestion}
inputProps={inputProps}
/>
)
}
}
// define the template only once
function getTemplate(options) {
function renderInput(locals) {
return <Auto {...locals} options={options} />
}
return t.form.Form.templates.textbox.clone({ renderInput })
}
// define the type
const Type = t.struct({
language: t.String
})
const options = {
fields: {
language: {
attrs: {
placeholder: 'Type C'
},
template: getTemplate({
getSuggestions,
getSuggestionValue,
renderSuggestion
})
}
}
}
It's clear now! Thank you!
I'd like to be able to use this with a custom input component such as react-autosuggest, but am not sure how I would do that. Is this possible?
This would be using react 0.14.6, autocomplete 3.4.0, and the latest version of tcomb-form. Thanks!