Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage with semantic ui #148

Closed
tkvw opened this issue Aug 31, 2017 · 28 comments
Closed

Usage with semantic ui #148

tkvw opened this issue Aug 31, 2017 · 28 comments

Comments

@tkvw
Copy link

tkvw commented Aug 31, 2017

Hello,

I like to use formik, but I use Semantic ui which does not emit onChange=>(event), but onChange=(event,data). See this discussion: Semantic-Org/Semantic-UI-React#638

Is it possible to support this?

@tkvw
Copy link
Author

tkvw commented Aug 31, 2017

Btw, created a quick'n dirty hack for this:
https://github.com/tkvw/formik/commit/d948afafb85b6ff6d2ed3598870aa41ac90d538d

@Andreyco
Copy link
Collaborator

Andreyco commented Aug 31, 2017

@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.

@tkvw
Copy link
Author

tkvw commented Aug 31, 2017

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).

@katpas
Copy link

katpas commented Sep 17, 2017

Is there any further update on this? Semantic + Formik support would be great!

@jaredpalmer
Copy link
Owner

Use setFieldValue

@tkvw
Copy link
Author

tkvw commented Sep 18, 2017

I am sorry, but I don't see how setFieldValue solves this issue?

@tkvw
Copy link
Author

tkvw commented Sep 18, 2017

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);
};

@katpas
Copy link

katpas commented Sep 20, 2017

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)}
          />

@jaredpalmer
Copy link
Owner

nice!

@davidhenley
Copy link

davidhenley commented Oct 9, 2017

@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}
/>
<Form.Input
  name="invoice"
  label="Invoice #"
  value={values.invoice}
  error={!!touched.invoice && !!errors.invoice}
  onChange={handleChange}
/>

@tkvw
Copy link
Author

tkvw commented Oct 10, 2017

@davidhenley : I created a simplified version at https://codesandbox.io/s/2022n8ol1r

@davidhenley
Copy link

davidhenley commented Oct 10, 2017

@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)
      }
}

@tkvw
Copy link
Author

tkvw commented Oct 10, 2017

@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 😄

@jaredpalmer
Copy link
Owner

just use this.props.name if blur event doesn't provide event.target.name 👍

@ivanberry
Copy link

Is any method integrate formik with semantic-ui?

@misto
Copy link

misto commented Sep 15, 2018

There's one which implements some basic functionality: https://www.npmjs.com/package/formik-semantic-ui

@mvanlonden
Copy link

mvanlonden commented Nov 8, 2018

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.

@gang89
Copy link

gang89 commented Nov 27, 2018

@mvanlonden how do you handle radio with the above wrapper? it cant seem to pass the value into the form state

@pixeltopic
Copy link

@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?

@Charchit26
Copy link

@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.

@sekunho
Copy link

sekunho commented Mar 9, 2019

@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.

eslint didn't like it though value: value || '' seems to work fine.

@eboutin
Copy link

eboutin commented Mar 18, 2019

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.

@hoffmanilya
Copy link

@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>

@agrarian-systems
Copy link

agrarian-systems commented May 8, 2020

@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.

@hoffmanilya
Copy link

@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
            })
        }}
    />)
};

@agrarian-systems
Copy link

agrarian-systems commented May 9, 2020

Works perfectly fine ! Thanks !
I'm still facing a few issues however...

  1. I got this warning : <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;
  1. 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 || '',

  2. The last question is about using Formik FieldArray. I don't manage to make it work... Any clues ?

Thanks for your help !

@hoffmanilya
Copy link

@agrarian-systems - glad to hear it.

  1. 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.

  2. What kind of problems are you seeing with FieldArray?

@ghirlekar
Copy link

@agrarian-systems

I made a slight change in your code. Any numerical field with initial value of 0 shows up as an empty string since value || '' captures 0 as a falsy value.
If you change value: value || '', to value: value ?? '', this fixes the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests