This is a placeholder issue. Form validation will be implemented apart from the Form update PR #400. We've had a few offline discussions surrounding the validation API. Ideas will be posted here soon as we're able.
Have any input regarding form validation? We'd love to hear your ideas.
Don't forget about i18n there.
Could you expound a little?
SUI form validation has default messages for shorthand rules.

There is must be ability for redefine these messages :hammer:
Gotcha, agreed.
Hi,
I am trying to write validation for form fields myself, and find this threads. It would be nice to have a simplified API outside Form as indicated on the top of this thread. But I think it would also be nice to provide some straight forward way/api to validate input fields manually. Maybe, like
<Button.Field label='email' name='email' validate={this.validateEmail} />
The validate method is called when the form is submit. When failed, the error message may be passed to its own state?
This was also my first inclination. Having the ability to validate a single field seems useful. I can see an equally valid argument that says, still validate the entire form model but only against a single field (e.g. do not specify validation for the other field names). What I'm less sure of is what the API looks like for wiring the model, field values, and error messages into the UI.
@davezuko did you begin work on validation the other day?
What I'm less sure of is what the API looks like for wiring the model, field values, and error messages into the UI.
generally, serializer may pick up these information from states of sub-components? just my 2ct.
Yep, generally it is clear. Specifics on the best way to do this is what I'm not clear about. Would love to see some experiments on this though. Goals being:
@levithomason - Not sure if this is beyond the scope of this issue, if so lmk and I can possibly open a new issue.
I was having some discussion with my team about building a form component on top of stardust forms, but given some of the things mentioned here I'm wondering how much we're willing to build directly into stardust. We're planning to do the work anyway, so would be cool to spread the love 馃槃
Just going to copy/paste a comment I wrote in our project, would love your thoughts on this:
Some thoughts:
Form component should be "controlled" meaning that the onChange updates the values key in local state.values when the form is submitted onFormSubmit (values) {
model.updateValues(values)
}
renderForm () {
<Form formSchema={formSchema} defaultValues={model.values}, onSubmit={this.onFormSubmit} />
}
{ type: 'text', name: 'firstName' } and imply label: _.startCase(name)Given a schema:
[
{ type: 'text', name: 'firstName' },
{ type: 'text', name: 'lastName' },
{ type: 'submit', name: 'Submit' }
]
We should render:
<Form>
<Form.Field control={Input} name='firstName' /* Other props */ />
<Form.Field control={Input} name='lastName' /* Other props */ />
<Form.Field control={Button}>Submit</Form.Field>
</Form>
For inputs that should show up next to each other:
[
{
type: 'group',
fields: [
{ type: 'text', name: 'firstName' },
{ type: 'text', name: 'lastName' }
],
},
{ type: 'text', name: 'title' },
{ type: 'submit', name: 'Submit' }
]
<Form>
<Form.Group widths='equal'>
<Form.Field control={Input} name='firstName' /* Other props */ />
<Form.Field control={Input} name='lastName' /* Other props */ />
</Form.Group>
<Form.Field control={Input} name='title' /* Other props */ />
<Form.Field control={Button}>Submit</Form.Field>
</Form>
For inputs that are collections (e.g. email addresses) :
[
{
type: 'group',
fields: [
{ type: 'text', name: 'firstName' },
{ type: 'text', name: 'lastName' }
],
},
{ type: 'text', name: 'title' },
{
type: 'nested',
name: 'emailAddresses',
fields: [
{ type: 'text', name: 'label' },
{ type: 'text', name: 'address' }
]
},
{ type: 'submit', name: 'Submit' }
]
<Form>
<Form.Group widths='equal'>
<Form.Field control={Input} name='firstName' /* Other props */ />
<Form.Field control={Input} name='lastName' /* Other props */ />
</Form.Group>
<Form.Field control={Input} name='title' /* Other props */ />
<Form.Group widths='equal'>
{
emailAddresses.map((emailAddress, index) => (
<Form.Field control={Input} name=`emailAddresses.${index}.label` /* Other props */ />
<Form.Field control={Input} name=`emailAddresses.${index}.address` /* Other props */ />
)
}
<Form.Field control={Button}>Add Email Address</Form.Field>
</Form.Group>
<Form.Field control={Button}>Submit</Form.Field>
</Form>
If you've gotten this far haha, regarding validations:
{ type: 'text', name: 'title', validation: (value) => value.length < 255 }{
type: 'group',
fields: [
{ type: 'text', name: 'firstName' },
{ type: 'text', name: 'lastName' }
],
validation: (data) => data.firstName || data.lastName
}
onChange, onBlur, or onSubmitThe nicest thing with this approach is that you're keeping everything in data and wouldn't need to touch the DOM. Using naming like collection.0.field you can use _.get(values, keyPath) and _.set(values, keyPath, value) to get/set values.
I wound up implementing some of the ideas I wrote about in the previous comment. See the PR that's referenced right above ^
Awesome, working on an appropriate response as well. Will post shortly.
This is definitely the right place, super thankful to see this. I agree with the goals here. I have mostly the same thoughts in terms of generating forms from an object, having a model, serializing on submit, validating that model, and showing errors. We also have a few form validation needs here at TA. Both in SPAs and static properties.
I'd like to create a set of packages that can work in any context, whether Node, SPA, or vanilla browser JS. I see a few separate pieces to this issue as a whole. We have already solved many of these problems at TA. We just need to pull them together, tweak some, and fill in missing parts. I'll layout what I see so far as the separate packages, then describe a potential workflow.
This is the Stardust Form and shorthand.
_Done_
_Needs_
I'm curious if the shorthand and/or Form/Field data/error coupling should be a separate wrapper component as well, <FormGenerator />? Not sure.
This is currently baked into the Form component. Try it here.
_Done_
_Needs_
We may have create something here, but TA has also solved this on a broader scale with Obey. Might be a long shot, but I want to explore pulling what we can from there. Maybe breaking things out for reuse.
_Done_
_Needs_
Again, Obey includes this. I'm thinking this should also be a separate package though. I just want a package that knows how to validate an object and returns the errors.
_Done_
_Needs_
If these are separate packages, you can use schema/serializer/validator you want. We'll just ship with a sane default set. You can also use these packages in any other customized workflow you want. We could just bundle them in a sane default workflow.
If we have a separate <FormGenerator /> (bad name) component, it would wrap these packages up in a workflow like described above. You could customize it's behavior. Perhaps showing inline error popups, or a single error message, etc. If it is not satisfactory, you can wrap the <Form /> yourself for a fully custom experience. This would also keep bloat out of the Form component.
Finally, since these packages are not DOM dependent, nor React dependent, you can use them in Node or a vanilla DOM experience. It also means that we can look to the community for pieces of the tool chain.
Is form validation currently possible?
+1. Really could do with this.
Formsy (https://github.com/christianalfoni/formsy-react) works really well for validation and form building in general. already has integrations for material UI (https://github.com/mbrookes/formsy-material-ui) and bootstrap.
Would be cool to make a semantic-ui-react integration??
This is certainly something I'd like to explore. A simple wrapper around their API may be the best bet.
I'm going to freeze this issue here. We've got some good notes and input above to work with. We've also got the advanced form work as a reference, #527.
The immediate next step should be a PR exploring a thin wrapper around formsy-react. Future convo should happen on PRs related to this work.
We're also considering options for implementing something similar to https://github.com/davezuko/react-reformed.
I've pulled the 1.0 milestone from this issue. There are a lot of great ways to validate forms with extra packages. You can search our issues to find several. These should be used for now.
We still plan on adding validation, it is just not a priority item for the core team ATM.
There's also my package called uniforms. Currently it's not using this package but only SUI classes, but the concept is to abstract schemas into unified schema called _bridge_ and provide many fields which are usable with any schema.
_(it's not an advertisement, but example of how to separate schema from components and DOM structure)_
I made a bunch of formsy-react wrappers for the form elements:
Closing for housekeeping. We're still interested in elegant form validation within Semantic UI React and welcome PRs. Until then, there are plenty of workarounds above.
I had the same struggle to do validation so i rolled out my own ADHOC validation, albeit, it can be better, one thing I struggled with, was that there is no clean API to update props outside of my render function, so each component needs a reference to the validator. The use of factories to create the components in SUI make them a bit of a locked box, if the form field could handle populating an error label it would great in addition to a label, here's some snippits:
Replace your Semantic Form with this ValidatingForm.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import _ from 'lodash'
import {
Form,
Label,
Menu } from 'semantic-ui-react'
import FormInput from './form_input'
class ValidatingForm extends React.Component {
validate() {
const form = this
const {
validators,
children
} = this.props
const findItem = (node, key) => {
let parent
let item
const getChildren = (node, key) => {
const parsedChildren = _.filter(node, item => item !== undefined && item !== false)
for(let child of parsedChildren) {
// console.log('child', child.type._meta)
parent = child
const children = _.get(child, 'props.children')
if (_.isObject(children) && key === _.get(children, 'props.name')){
item = children
} else if (_.get(child, 'props.name') && key === _.get(child, 'props.name')) {
item = child
break
} else if (_.isArray(children)) {
getChildren(children, key)
}
}
}
getChildren(node, key)
return { child: item, parent }
}
const newValidators = { }
Object.entries(validators).forEach(([key, item]) => {
const { child, parent } = findItem(children, key)
const value = _.get(child, 'props.value') || _.get(child, 'props.defaultValue')
const name = _.get(child, 'props.name')
const f = _.get(validators[name],'validator')
const message = _.get(validators[name],'message')
const isValid = _.isFunction(f) ? f(value) : undefined
console.log(item.isValid, _.isFunction(f) ? f(value) : undefined)
newValidators[name] = { validator: f, message, isValid }
})
form.props.onValidate(newValidators)
const result = Object.entries(validators).find(([key, item]) => !item.isValid)
return !(result && result.length > 0)
}
render() {
return (<Form {...this.state} {...this.props} />)
}
}
import React, { Children, cloneElement, Component } from 'react'
import cx from 'classnames'
import _ from 'lodash'
import PropTypes from 'prop-types'
import {
childrenUtils,
createHTMLInput,
createShorthandFactory,
customPropTypes,
getElementType,
getUnhandledProps,
META,
partitionHTMLProps,
SUI,
useKeyOnly,
useValueAndKey,
} from 'semantic-ui-react/dist/commonjs/lib'
import { Button, Icon, Label, FormField } from 'semantic-ui-react'
class Input extends Component {
constructor(props) {
super(props)
}
static propTypes = {
as: customPropTypes.as,
action: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),
actionPosition: PropTypes.oneOf(['left']),
children: PropTypes.node,
className: PropTypes.string,
disabled: PropTypes.bool,
error: PropTypes.bool,
fluid: PropTypes.bool,
focus: PropTypes.bool,
icon: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),
iconPosition: PropTypes.oneOf(['left']),
input: customPropTypes.itemShorthand,
inverted: PropTypes.bool,
label: customPropTypes.itemShorthand,
labelPosition: PropTypes.oneOf(['left', 'right', 'left corner', 'right corner']),
loading: PropTypes.bool,
onChange: PropTypes.func,
size: PropTypes.oneOf(SUI.SIZES),
tabIndex: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
transparent: PropTypes.bool,
type: PropTypes.string,
validate: PropTypes.func
}
static defaultProps = {
type: 'text',
// validate: _.noop
}
static _meta = {
name: 'Input',
type: META.TYPES.ELEMENT,
}
computeIcon = () => {
const { loading, icon } = this.props
if (!_.isNil(icon)) return icon
if (loading) return 'spinner'
}
computeTabIndex = () => {
const { disabled, tabIndex } = this.props
if (!_.isNil(tabIndex)) return tabIndex
if (disabled) return -1
}
focus = () => (this.inputRef.focus())
handleChange = (e) => {
const value = _.get(e, 'target.value')
_.invoke(this.props, 'onChange', e, { ...this.props, value })
}
handleChildOverrides = (child, defaultProps) => ({
...defaultProps,
...child.props,
ref: (c) => {
_.invoke(child, 'ref', c)
this.handleInputRef(c)
},
})
handleInputRef = c => (this.inputRef = c)
partitionProps = () => {
const { disabled, type, validate } = this.props
const tabIndex = this.computeTabIndex()
const unhandled = getUnhandledProps(Input, this.props)
const [htmlInputProps, rest] = partitionHTMLProps(unhandled)
return [{
...htmlInputProps,
disabled,
validate,
type,
tabIndex,
onChange: this.handleChange,
ref: this.handleInputRef,
}, rest]
}
render() {
const {
action,
actionPosition,
children,
className,
disabled,
error,
fluid,
focus,
icon,
iconPosition,
input,
inverted,
label,
labelPosition,
loading,
size,
transparent,
type,
validate
} = this.props
const classes = cx(
'ui',
size,
useKeyOnly(disabled, 'disabled'),
useKeyOnly(error, 'error'),
useKeyOnly(fluid, 'fluid'),
useKeyOnly(focus, 'focus'),
useKeyOnly(inverted, 'inverted'),
useKeyOnly(loading, 'loading'),
useKeyOnly(transparent, 'transparent'),
useValueAndKey(actionPosition, 'action') || useKeyOnly(action, 'action'),
useValueAndKey(iconPosition, 'icon') || useKeyOnly(icon || loading, 'icon'),
useValueAndKey(labelPosition, 'labeled') || useKeyOnly(label, 'labeled'),
'input',
className,
)
const ElementType = getElementType(Input, this.props)
const [htmlInputProps, rest] = this.partitionProps()
// Render with children
// ----------------------------------------
if (!childrenUtils.isNil(children)) {
// add htmlInputProps to the `<input />` child
const childElements = _.map(Children.toArray(children), (child) => {
if (child.type !== 'input') return child
return cloneElement(child, this.handleChildOverrides(child, htmlInputProps))
})
return <ElementType {...rest} validate={this.props.validate} className={classes}>{childElements}</ElementType>
}
// Render Shorthand
// ----------------------------------------
const actionElement = Button.create(action)
const labelElement = Label.create(label, {
defaultProps: {
className: cx(
'label',
// add 'left|right corner'
_.includes(labelPosition, 'corner') && labelPosition,
),
},
})
return (
<ElementType validate={this.props.validate} {...rest} className={classes}>
{actionPosition === 'left' && actionElement}
{labelPosition !== 'right' && labelElement}
{createHTMLInput(input || type, { defaultProps: htmlInputProps })}
{actionPosition !== 'left' && actionElement}
{Icon.create(this.computeIcon())}
{labelPosition === 'right' && labelElement}
</ElementType>
)
}
}
Input.create = createShorthandFactory(Input, type => ({ type }))
class FormInput extends Component {
static _meta = {
name: 'FormInput',
parent: 'Form',
type: META.TYPES.COLLECTION,
}
static defaultProps = {
as: FormField,
control: Input
}
static propTypes = {
as: customPropTypes.as,
control: FormField.propTypes.control,
validator: PropTypes.obj
}
render(){
const { control } = this.props
const errorStyle = { background: 'none',
color: '#9f3a38',
fontSize: '0.7em',
verticalAlign: 'top',
lineHeight: '14px',
textTransform: 'uppercase'
}
const { error, label, validator: { isValid, message }, ...rest } = { ...getUnhandledProps(FormInput, this.props) }
const errorLabel = <Label style={{ background:'none', color:'black', fontSize: '1em' }}
content={<span>{label} <span style={errorStyle}>{message}</span>
</span>}/>
const newProps = { ...rest, error: error || isValid === false, label: isValid === false ? errorLabel : label }
const ElementType = getElementType(FormInput, newProps)
return <ElementType {...newProps} control={control} />
}
}
export default FormInput
To use, you i had to just import and muck with the Input, this only works with Input type fields.
To use:
validators: {
isbn13: isIsbn13,
title: isRequired,
author: isRequired,
weeklyPrice: isRequired,
price: isRequired
onValidate = (validators) => {
this.setState({ validators })
}
<ValidatingForm ref="book_form"
onValidate={this.onValidate}
validators={this.state.validators}
</ValidatingForm>
<FormInput
onChange={this.handleChange}
onInput={this.handleChange}
width={5}
name="isbn13"
value={isbn13}
fluid
label="ISBN13 *"
type="text"
placeholder="ISBN13"
validator={validators.isbn13}
/>
export const isRequired = {
validator: (item = '') => {
console.log(item)
return !(_.isEmpty(item))
},
message: 'Required'
}
export const isIsbn13 = {
validator: (item = '') => {
return !!_.get(item.match(/9\d{12}(?=(\b|[^\d]))/), '0')
},
message: 'Please enter a valid ISBN13'
}
book_form.refs.validate()
Hope this helps, validation is a pretty important feature for forms, and the biggest lacking feature IMHO. Any ideas how to improve this gladly accepted.
Hope this helps
Most helpful comment
I made a bunch of formsy-react wrappers for the form elements:
formsy-semantic-ui-react