Closed benmonro closed 8 years ago
Personally I'd prefer handle dynamic forms in a centralized and declarative way rather than scatter the logic amongst the components.
For example, this is the general form of a dynamic form:
function getType(value) {
//...return the type based on value
}
function getOptions(value) {
//...return the options based on value
}
var App = React.createClass({
getState(value) {
return {
value,
type: getType(value),
options: getOptions(value)
};
},
getInitialState() {
return this.getState({});
},
onChange(value) {
this.setState(this.getState(value));
},
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
console.log(value);
}
},
render() {
return (
<form onSubmit={this.onSubmit}>
<t.form.Form
ref="form"
type={this.state.type}
options={this.state.options}
value={this.state.value}
onChange={this.onChange}
/>
<button type="submit" className="btn btn-primary">Save</button>
</form>
);
}
});
Now in your use case
Suppose you have a country Select box. If the user choose US, you might want it to change the options of a 'State' select box with all of the US states. But then if they choose Canada maybe it changes it to 'provinces' etc
would be
const Country = t.enums({
US: 'United States',
CA: 'Canada'
});
const State = t.enums({
AL: 'Alabama',
AK: 'Alaska'
// and so on...
});
const Province = t.enums({
A: 'Province A',
B: 'Province B'
// and so on...
});
function getType(value) {
var props = {
country: Country
};
switch (value.country) {
case 'US' :
props.state = State;
break;
case 'CA' :
props.province = Province;
break;
}
return t.struct(props);
}
function getOptions(value) {
return {};
}
I found this pattern neat and clear. What do you think?
Remark. The above getType
function is not optimised (it's just the more general form I can think of) but you can optimise based on the particular use case
const NoCountry = t.struct({
country: Country
});
const US = NoCountry.extend({
state: State
});
const CA = NoCountry.extend({
province: Province
});
function getType(value) {
switch (value.country) {
case 'US' : return US;
case 'CA' : return CA;
default : return NoCountry;
}
}
well, the problem I have w/ that approach is that our forms are defined by the user and sent to us as a message. Each message will have the schema, options & value. So we really need to define the change handlers in the options for each component. All of the messages will be processed by the same form. So while 1 form might have Country/State another might have something completely different that behaves in a totally different way. So my goal is to create reusable change handlers, transformers, templates & components that can be used by providing a string in a message (which will then be converted to a require).
_feature request_
It would be nice is to get the tcomb context in an event handler on an individual component.
I did this in a demo by just adding an 'onChange' event to my options and then in the form on change I did the following:
Even though I was able to accomplish this myself, I was just thinking it would be nice if tcomb would automatically do this for me so that my form onChange wouldn't have to do this...
This way the component that was changed is responsible for handling it's events but still has access to tcomb in it's context. The use case for this is that we want to have dynamic elements in our form where when one value in the form changes, it will modify how another element is rendered, or if it's even rendered at all. However, we don't want this to be the responsibility of the form, but rather the component that changed. For example. Suppose you have a country Select box. If the user choose US, you might want it to change the options of a 'State' select box with all of the US states. But then if they choose Canada maybe it changes it to 'provinces' etc. This is just one example. We will have various use cases where form elements can be defined as 'dynamic' by the user so we really need to handle this in the component options and not at the form level.