Closed dajomu closed 9 years ago
+1 on this, could not find examples of how to define a form with remote structs.
If it's not there yet maybe you could point out some entry points of where to start implementing this in tcomb-form
.
a typeahead section at the bottom
remote structs
Hi @dajomu @kompot Not sure what you want to achieve, could you please provide an example and/or more details?
Yeah, say we have two entities: Person
and Roles
such as
const Role = t.struct({
id: t.Num,
name: t.Str
});
const Person = t.struct({
id: t.Num,
username: t.Str,
roles: t.list(Role)
});
List of roles might be very long. So I'd like to implement it with some typeahead control like react-select
http://jedwatson.github.io/react-select/
which would fetch roles from remote source as you type.
I was asking the same here about how to integrate this widget with tcomb-form https://github.com/gcanti/tcomb-form/issues/82#issuecomment-105638968
Hi everyone, this is the simplest example of integration I can think of.
Define a custom factory:
// react-select-factory.js
'use strict';
var Select = require('react-select');
var t = require('tcomb-form');
class ReactSelect extends t.form.Select {
getTemplate() {
return (locals) => { // <- locals contains the "recipe" to build the UI
// handle error status
var className = 'form-group';
if (locals.hasError) {
className += ' has-error';
}
// translate the option model from tcomb to react-select
var options = locals.options.map(({value, text}) => ({value, label: text}));
return (
<div className={className}>
<label className="control-label">{locals.label}</label>
<Select
name={locals.attrs.name}
value={locals.value}
options={options}
onChange={locals.onChange}
/>
</div>
);
};
}
}
module.exports = ReactSelect;
Usage:
// app.js
var React = require('react');
var t = require('tcomb-form');
var ReactSelect = require('./react-select-factory');
var Form = t.form.Form;
var Gender = t.enums.of('Male Female');
var Person = t.struct({
name: t.Str,
surname: t.Str,
gender: Gender
});
var options = {
fields: {
gender: {
factory: ReactSelect // <- use my custom factory
}
}
};
var App = React.createClass({
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
console.log(value);
}
},
render() {
return (
<form onSubmit={this.onSubmit}>
<Form ref="form"
type={Person}
options={options}
/>
<button className="btn btn-primary">Save</button>
</form>
);
}
});
React.render(<App />, document.getElementById('app'));
I'll add more documentation about custom factories when I'll find the time. If someone of you guys wants to help me out writing the docs I'd be really grateful.
Thank you, I'll see if I can adapt the above solution to my needs. I'll let you know how I get on...
Hi, I'm now working on this problem and I seem to have an issue with the getTemplate() method. It doesn't seem to be getting used. I still seem to be getting the template for a simple select box, so I guess that the ReactSelect is not getting used. I'm not getting any console errors, so I'm guessing that the getTemplate method is not overriding the normal template.
Could you put up a gist so I can try to help you out?
Here's a link to the gist - https://gist.github.com/dajomu/5f4dd55a3ee6f1cacbc5
It may be missing some bits of code to make the actual react component function properly (I've copy/pasted bits from a settings screen component for simplicity), but this has all of the relevant code that I'm using to generate the form.
My idea was to create the dropdown choices by making PostCodeSelect a function that generates the enum from a set of options. These options would be obtained from an API call that is triggered by the locals.onChange method. I haven't yet looked in to how to pass a method on via that yet...
Here's a link to the gist...
This is working for me (code rearranged):
import React from 'react';
import t from 'tcomb-form';
import ReactSelect from './ReactSelect'; // <= using exactly you file ReactSelect.js
const Form = t.form.Form;
const LocationForm = (model) => ({
fields: {
postcode: {
factory: ReactSelect
}
}
});
const PostCodeSelect = t.enums({
please: 'E17 3AA',
god: 'NW6 6RF',
no: 'N6 5BB'
});
const LocationModel = () => t.struct({
postcode: PostCodeSelect
});
const model = LocationModel();
const form = LocationForm(model);
const value = {postcode: "please"};
const App = React.createClass({
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
console.log(value);
}
},
render() {
return (
<form onSubmit={this.onSubmit}>
<Form ref="form"
type={model}
options={form}
value={value}
/>
<button className="btn btn-primary">Save</button>
</form>
);
}
});
React.render(<App />, document.getElementById('app'));
These options would be obtained from an API call that is triggered by the locals.onChange method...
working on this...
It's really odd, so there are no real changes from my code to yours, but all I seem to get is the familiar selection that comes with tcomb, rather than anything fancy. I've just tried chucking out all of my code and replacing it with the code you pasted above and I still get the wrong template. I'm using tcomb 0.4.11, would that be the cause of these problems?
I'm using tcomb 0.4.11
Ah! I'm using the latest version (v0.5.4). Can you upgrade?
Otherwise with v0.4.x you could try this (not tested):
const LocationForm = (model) => ({
fields: {
postcode: {
//factory: ReactSelect
template: function (locals) { // place here the code of the function returned by getTemplate
// handle error status
var className = 'form-group';
if (locals.hasError) {
className += ' has-error';
}
...
}
}
}
});
I can't update it just at the moment (dependencies and time pressure). I'll attempt your solution above. Thanks very much for the help!
Thanks, just to let you know this recipe works great!
Great, thanks for your feedback
Is there plain JavaScript solution and or a solution that relies on React's preference for composition over prototypical inheritance?
I'm referring to this: class ReactSelect extends t.form.Select
React's documentation provides no path for inheritance or extending in this way. And prefers composition.
Also I'm just not as adept at translating coffeescript to something I can use in plain JavaScript.
What I've done so far is some jankiness:
var React = require('react'),
_ = require('lodash'),
t = require('tcomb-form'),
Select = t.form.Select;
Select.prototype.getTemplate = function() {
return function(locals) {
return (<div>hello</div>);
};
};
module.exports = Select;
Not exactly the way to go! :-)
Hi, This repo and the examples are written in ES6 (*) not coffeescript.
React's preference for composition
My preference as well. Sometimes classes are useful though.
(*) and a pinch of ES7
@jwaggener if you are still interested this is maybe what you are looking for:
Minimal custom factory interface
A React component such that:
Props:
type
: a tcomb typeoptions
: the options for this particular fieldvalue
: the current valueonChange(value, path) => void
: call this when the value changesctx
: an object generated by tcomb-form containing useful infosMethods:
() => ReactElement
() => ValidationResult
(see tcomb-validation library)Laws:
onChange(newValue, path)
(you can find the right path
value in the ctx
prop)Notes:
validate
method, your component will be stateful@gcanti Thank you. I'll look at this. That is what I was asking about.
I also incorporated babelify in my build so I can utilize and follow ES6 features and syntax.
Fun! Thanks.
:+1:
@gcanti
I found it very easy to implement the interface. I do not understand the purpose of the Law to call onChange.
I found that calling setState({value: myVal}) when changing the value and then returning a tcomb validation with the validate method was sufficient. When I invoke myForm.getValue() I was able to log the changed value.
Why do I need to call onChange? And is that method injected by tcomb-form
https://gist.github.com/jwaggener/e311a3fbe14b3e5a462d
Thanks for this great lib!
Why do I need to call onChange? And is that method injected by tcomb-form
Yes it's injected by tcomb-form's top level API t.form.Form
(I'll add this remark to the docs, thanks).
A t.form.Form
component can behave like a controlled component (say you want modify the value or the options on the fly).
Relevant example (disable a field based on another field's value):
https://github.com/gcanti/tcomb-form/blob/master/GUIDE.md#rendering-options
Hi, I'm trying to create a list with a typeahead section at the bottom. Do you have any suggestions as to how I would achieve this? Would I have to use a custom factory for the list?
I apologise if this is a bit of a vague request.