gcanti / tcomb-form

Forms library for react
https://gcanti.github.io/tcomb-form
MIT License
1.16k stars 136 forks source link

Help needed with multiselects #220

Closed hhumphrey84 closed 8 years ago

hhumphrey84 commented 8 years ago

I'm a bit stuck. I'm creating a list of enums as so:

module.exports = t.struct({
    name: t.Str,
    gender: Gender,
    players: t.list(t.enums({})),
    _id: t.Str
});

I'm populating this list with my dropdown but my question is, how would I pre-populate this with players already in the team?

Sorry....I'm sure the answer is really simple!

Thanks, Helen

nerdstep commented 8 years ago

Should be something like this:

var FormView = React.createClass({
  getInitialState: function () {
    return {    
      value: {
        name: 'Foobar',
        players: [0,1,2] // your enum key values
      }
    };
  },
  render: function () {
    // set the initial values on the form
    return(<div><Form value={this.state.value} /></div>);
  }
});
hhumphrey84 commented 8 years ago

Sorry for the delay in getting back to you. This put me on the right track, and I was able to set my default list quite easily in the end.

A further issue has arisen with validating my list of enums as this list is populated after an API call:

newOptions = t.update(this.state.options, {
    fields: {
    players: {
        item: {    options: { '$set': allPlayers } }
    }
    }
});

this.setState({ options: newOptions });

allPlayers being the return value from the API call. This works fine, I have my multi select with my select dropdown populated with the names of all the potential players.

However if I click submit the tcomb validation is checking if the values selected match the values in the original enum object....which if course was an empty object waiting to be populated:

Enums.is = function (x) {
    return map.hasOwnProperty(x);
};

Any idea what I'm doing wrong? Apologies again!

gcanti commented 8 years ago

If I understand, a simple solution would be to just use a list of strings (since you populate the options it's not possible to get type errors):

const Type = t.struct({
  name: t.String,
  players: t.list(t.String) // <= just a list of strings
});

const initialPlayers = [
  {value: 'A', text: 'Player A'},
  {value: 'B', text: 'Player B'},
  {value: 'C', text: 'Player C'},
  {value: 'D', text: 'Player D'}
];

const allPlayers = [
  {value: 'A', text: 'Player A'},
  {value: 'B', text: 'Player B'},
  {value: 'C', text: 'Player C'},
  {value: 'D', text: 'Player D'},
  {value: 'E', text: 'Player E'},
  {value: 'F', text: 'Player F'},
  {value: 'G', text: 'Player G'},
  {value: 'H', text: 'Player H'},
  {value: 'I', text: 'Player I'},
  {value: 'L', text: 'Player L'}
];

var App = React.createClass({

  getInitialState() {
    return {
      value: {
        name: 'Foo',
        players: ['A', 'C']
      },
      options: {
        fields: {
          players: {
            item: {
              factory: t.form.Select,
              options: initialPlayers
            }
          }
        }
      }
    };
  },

  componentDidMount() {
    // fake API call, will change the options of all the selects
    setTimeout(() => {
      this.setState({
        options: t.update(this.state.options, {
          fields: {
            players: {
              item: {
                options: {$set: allPlayers}
              }
            }
          }
        })
      });
    }, 2000);
  },

  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={Type}
          options={this.state.options}
          value={this.state.value}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    );
  }

});

If you really want to use an enums and to update the type after the API call you could store the current type in the state:

function getEnumsFromOptions(options) {
  return t.enums.of(options.map((option) => option.value));
}

const initialPlayers = [
  {value: 'A', text: 'Player A'},
  {value: 'B', text: 'Player B'},
  {value: 'C', text: 'Player C'},
  {value: 'D', text: 'Player D'}
];

const allPlayers = [
  {value: 'A', text: 'Player A'},
  {value: 'B', text: 'Player B'},
  {value: 'C', text: 'Player C'},
  {value: 'D', text: 'Player D'},
  {value: 'E', text: 'Player E'},
  {value: 'F', text: 'Player F'},
  {value: 'G', text: 'Player G'},
  {value: 'H', text: 'Player H'},
  {value: 'I', text: 'Player I'},
  {value: 'L', text: 'Player L'}
];

var App = React.createClass({

  getInitialState() {
    return {
      type: t.struct({ // <= store the type in the state
        name: t.String,
        players: t.list(getEnumsFromOptions(initialPlayers))
      }),
      value: {
        name: 'Foo',
        players: ['A', 'C']
      },
      options: {
        fields: {
          players: {
            item: {
              factory: t.form.Select,
              options: initialPlayers
            }
          }
        }
      }
    };
  },

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        type: t.struct({ // <= update also the type
          name: t.String,
          players: t.list(getEnumsFromOptions(allPlayers))
        }),
        options: t.update(this.state.options, {
          fields: {
            players: {
              item: {
                options: {$set: allPlayers}
              }
            }
          }
        })
      });
    }, 2000);
  },

  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}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    );
  }

});
hhumphrey84 commented 8 years ago

Brilliant, thanks for your help. I've been able to get what I needed up and running using the information above.

gcanti commented 8 years ago

:+1: