Hello,
I like to use formik, but I use Semantic ui which does not emit onChange=>(event), but onChange=(event,data). See this discussion: https://github.com/Semantic-Org/Semantic-UI-React/issues/638
Is it possible to support this?
Btw, created a quick'n dirty hack for this:
https://github.com/tkvw/formik/commit/d948afafb85b6ff6d2ed3598870aa41ac90d538d
@tkvw
Docs say event argument is of SyntheticEvent type which is exactly what Formik's onChange listener accepts. What information does data argument carries? I cannot assume it's content based on docs, unfortunately. Isn't it OK to "ingore" it?
You can create intermediate component which will handle these API inconsistencies for you.
It's very common to create wrapper component over base input component - makes refactoring, etc. very pleasant.
That's correct, the event argument is of SyntheticEvent, but it does not have a target. So the default handleChange supplied by Formnik fails. I can't imagine how to create a Hoc for this, because it should overwrite behaviour of handleChange in the Formnik class. I can't update the state of this encapsulated component?
The relevant quote from the discussion I've posted:
The event.target is a browser event property. However, many Semantic UI components, such as a Dropdown, Checkbox, and Radio do not work directly with native browser form controls such as input and select. They are built using stylized markup and custom internal state.
Because of this, there are no native browser events available for certain callbacks. This is why all change events in Semantic-UI-React provide the event first (when available) but also a second argument which contains the data you need. You should never have to access the native browser event for most tasks.
So, the second argument contains all the relevant target data (actually, everything you are looking from in event.target).
Is there any further update on this? Semantic + Formik support would be great!
Use setFieldValue
I am sorry, but I don't see how setFieldValue solves this issue?
Actually, nevermind. I created a hoc for this:
import React from 'react';
import {Formik} from 'formik';
export const semanticFormik = ({mapPropsToValues,...props}) => WrappedComponent => {
let pristineValues;
const keepPristineValues = (p)=>{
pristineValues = mapPropsToValues(p);
return pristineValues;
}
class Bridge extends React.Component{
handleBlur(event,data){
this.setValue(data);
}
handleChange(event,data){
this.setValue(data);
}
setValue(data){
if(data.name){
this.props.setFieldValue(data.name,data.value);
this.props.setFieldTouched(data.name,!this.isPristineValue(data.name,data.value));
}
}
isPristineValue (fieldName,value){
if(pristineValues[fieldName] === undefined){
return value === '' || value === null || value === undefined;
}
return pristineValues[fieldName] === value;
}
render(){
const {handleChange,handleBlur,...props} = this.props;
return <WrappedComponent
handleChange={this.handleChange.bind(this)}
handleBlur={this.handleBlur.bind(this)}
{...props}
/>;
}
}
return Formik({mapPropsToValues:keepPristineValues,...props})(Bridge);
};
We wrote this change handler for the dropdown and then called it on change which worked. setFieldValue comes from the Formik functions which we're wrapping our form component in.
const handleDropdownChange = (e, { name, value }) => setFieldValue(name, value)
Then called it:
<FormikDropdown
name="dueNumberOf"
placeholder="2"
value={values.dueNumberOf}
onChange={handleDropdownChange}
onBlur={handleBlur}
errors={errors.dueNumberOf}
options={createOptions(dueNumberOfValues)}
/>
nice!
@tkvw how do you use this wrapper? For example, how do I add an input and a dropdown while using this wrapper you wrote? Right now, I have to do the following??:
const handleBlur = (e, { name, value }) => setFieldTouched(name, value);
const handleChange = (e, { name, value }) => setFieldValue(name, value);
<Form.Dropdown
selection
name="type"
label="Request Type"
options={typeOptions}
value={values.type}
error={!!touched.type && !!errors.type}
onBlur={handleBlur}
onChange={handleChange}
/>
md5-9369ba0455a271560164070c09dbafb1
<Form.Input
name="invoice"
label="Invoice #"
value={values.invoice}
error={!!touched.invoice && !!errors.invoice}
onChange={handleChange}
/>
@davidhenley : I created a simplified version at https://codesandbox.io/s/2022n8ol1r
@tkvw both of these examples set touched and dirty back to false when you change back to initial values. Dirty and touched should stay true after you touch them.
What's the thought process behind checking for pristine values, instead of just saying setFieldTouched(name, true)??
Also, this will not work for input, because Form.Input does not have a data.name for onBlur, it has only a e.target.name
So on the first one, I changed this to work:
handleBlur(event, data) {
if (data) {
this.setValue(data);
} else {
this.props.setFieldTouched(event.target.name, true)
}
}
@davidhenley : this is just a pointer to a 'hocable' solution for using semantic ui with Formik. There's no magic, just implement (and extend) the logic as you wish and use the withSemanticFormik hoc on all your forms. And if you created a super-duper implementation, then please share 馃槃
just use this.props.name if blur event doesn't provide event.target.name 馃憤
Is any method integrate formik with semantic-ui?
There's one which implements some basic functionality: https://www.npmjs.com/package/formik-semantic-ui
Created a simple Field wrapper
const SemanticField = ({ component, ...fieldProps }) => (
<Field
{...fieldProps}
render={({
field: { value, onBlur, ...field },
form: { setFieldValue, setFieldTouched },
...props
}) =>
React.createElement(component, {
...fieldProps,
...field,
...props,
...(typeof value === 'boolean'
? {
checked: value
}
: {
value
}),
onChange: (e, { value: newValue, checked }) =>
setFieldValue(fieldProps.name, newValue || checked),
onBlur: (e, blurProps) =>
blurProps ? setFieldTouched(fieldProps.name, blurProps.value) : onBlur(e)
})
}
/>
);
so you can just
<SemanticField
name="name"
component={Select}
options={options}
/>
inside the Formik component.
Should work for _almost_ all semantic ui form components.
@mvanlonden how do you handle radio with the above wrapper? it cant seem to pass the value into the form state
@mvanlonden For some reason if I backspace to the point where there is nothing on an input component, when using that wrapper I get a react error.
A component is changing a controlled input of type password to be uncontrolled.
Otherwise, it works great. Do you know what might be the issue?
@mvanlonden For some reason if I backspace to the point where there is nothing on an input component, when using that wrapper I get a react error.
A component is changing a controlled input of type password to be uncontrolled.Otherwise, it works great. Do you know what might be the issue?
Replacing value in SemanticField wrapper with value: value? value : '' worked for me.
@mvanlonden For some reason if I backspace to the point where there is nothing on an input component, when using that wrapper I get a react error.
A component is changing a controlled input of type password to be uncontrolled.
Otherwise, it works great. Do you know what might be the issue?Replacing
valuein SemanticField wrapper withvalue: value? value : ''worked for me.
eslint didn't like it though value: value || '' seems to work fine.
For those who land here like I did a few days ago because they're struggling to make Formik and Semantic-UI React work together: I pieced together a working solution based on mvanlonden's solution:
formik-with-semantic-ui-react-form
Comments are welcome.
@gang89 - based on the solutions from @mvanlonden and @eboutin I've come up with the following that seems to work with radio groups. I'm still testing this but so far, so good.
const SemanticField = ({ component, ...fieldProps }) => (
<Field
{...fieldProps}
render={({
field: { value, onBlur, ...field },
form: { setFieldValue, submitCount, touched, errors, handleBlur },
...props
}) =>
React.createElement(component, {
...fieldProps,
...field,
...props,
...(component.name === 'FormRadio'
? {
checked: fieldProps.value === value
}
: {
value: value ? value : ''
}
),
...((submitCount > 1 || touched[field.name]) && errors[field.name]
? {
error: {
content: errors[field.name]
}
}
: {}
),
onChange: (e, { value: newValue, checked }) =>
setFieldValue(fieldProps.name, newValue ? newValue : checked),
onBlur: handleBlur
})}
/>
);
<Form.Group grouped>
<label>Role</label>
<SemanticField name="role" value='admin' label='Admin' component={Form.Radio} />
<SemanticField name="role" value='user' label='User' component={Form.Radio} />
</Form.Group>
@hoffmanilya thanks for your sharing this piece of code. Just tried it today, it works great but when I check a Form.Checkbox, and then uncheck the value remains true. I'm sorry, I don't have enough experience to contribute on this but just wanted to mention it ;) Thanks to all of you.
@agrarian-systems - you can try the following. In my testing it works with both check boxes and radio groups.
const SemanticField = ({ component, ...fieldProps }) => {
const { showErrorsInline, ...rest } = fieldProps
return (<Field
{...rest}
render={({
field: { value, onBlur, ...field },
form: { setFieldValue, submitCount, touched, errors, handleBlur },
...props
}) => {
return React.createElement(component, {
...rest,
...field,
...props,
...(component === SemanticForm.Radio || component === SemanticForm.Checkbox
? {
checked: component === SemanticForm.Radio ? fieldProps.value === value : value
}
: {
value: value || ''
}
),
...((submitCount >= 1 || touched[field.name]) && errors[field.name]
? {
error: showErrorsInline == false ? true : {
content: errors[field.name]
}
}
: {}
),
onChange: (e, { value: newValue, checked }) =>
setFieldValue(fieldProps.name, newValue || checked),
onBlur: handleBlur
})
}}
/>)
};
Works perfectly fine ! Thanks !
I'm still facing a few issues however...
<Field render> has been deprecated and will be removed in future versions of Formik. Please use a child callback function instead.So I changed it a little bit :
import React from 'react';
import { Field } from 'formik';
import { Form } from 'semantic-ui-react';
const SemanticField = ({ component, ...fieldProps }) => {
const { showErrorsInline, ...rest } = fieldProps;
return (
<Field {...rest}>
{({
field: { value, onBlur, ...field },
form: { setFieldValue, submitCount, touched, errors, handleBlur },
...props
}) => {
return React.createElement(component, {
...rest,
...field,
...props,
...(component === Form.Radio || component === Form.Checkbox
? {
checked:
component === Form.Radio ? fieldProps.value === value : value,
}
: {
value: value || '',
}),
...((submitCount >= 1 || touched[field.name]) && errors[field.name]
? {
error:
showErrorsInline == false
? true
: {
content: errors[field.name],
},
}
: {}),
onChange: (e, { value: newValue, checked }) =>
setFieldValue(fieldProps.name, newValue || checked),
onBlur: handleBlur,
});
}}
</Field>
);
};
export default SemanticField;
When using Form.Dropdown, it works but says :
Dropdown value must be an array when multiple is set. Received type: [object String].
I guess it has to do with value: value || '',
The last question is about using Formik FieldArray. I don't manage to make it work... Any clues ?
Thanks for your help !
@agrarian-systems - glad to hear it.
I don't use multiple drop-downs but you can probably create another condition that sets the field's value to an empty array if the component is a Form.Dropdown and multiple is set to true. From what I remember the default string value was necessary to properly handle null field values.
What kind of problems are you seeing with FieldArray?
Most helpful comment
@davidhenley : I created a simplified version at https://codesandbox.io/s/2022n8ol1r