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

Creating RRS Reusable Formik Field or Component #147

Closed
carlospence opened this issue Jun 30, 2020 · 13 comments
Closed

Creating RRS Reusable Formik Field or Component #147

carlospence opened this issue Jun 30, 2020 · 13 comments

Comments

@carlospence
Copy link

I Created a Formik Component for RRS, so as to reuse it instead of repeating so many codes while creating form but whenever I use the component, it slows down every other components on the form. If I use the RRS component directly on the form, it is fast. When I now used Formik Component, it was slightly faster but still slow.

This is the reuseable formik component code

import * as React from 'react'
import RRS from 'react-responsive-select';
import { Formik, Form, Field, FieldProps } from 'formik'
import { FormFeedback, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText, Label } from "reactstrap";
import "react-responsive-select/dist/ReactResponsiveSelect.css";
import { ErrorIcon, CaretIcon } from './Icons'


const SyncRRSInput = ({
    field, 
    iconOrText,
    form: { touched, errors, setFieldValue, values, handleBlur }, .
    ...props
}) => {
    const { options, isRequired = false, label, showLabel = false, multiselect = false } = props
    const icon = iconOrText ?? ""
    let error = errors[field.name];
    let touch = touched[field.name];
    let currentValue = values[field.name];

    return (
        <FormGroup>

            {showLabel ? <Label for={props.id || props.name} className={props.labelColor}>{label}</Label> : ""}
            {/* <label htmlFor={props.id || props.name}>{label}</label> */}

            <RRS
                {...field}
                {...props}
                options={options}
                selectedValue={values[field.name]}

                caretIcon={<CaretIcon key={`${field.name}-caret`} />}
                onChange={({ value, name }) => {
                    if (props.onSelectChange) {
                        props.onSelectChange(value)
                    }
                    if (value) {
                        //setFieldValue(field.name, multiselect ? option.map(x => x.value) : (option).value || "")
                        setFieldValue(field.name, value)
                    }
                    else {
                        setFieldValue(field.name,  "")
                    }
                }}
                onBlur={({ value, name }) => {
                    handleBlur({ target: { value, name } });
                }}

            />


            {touched[field.name] && errors[field.name] && (
                <div className="error text-danger">&nbsp;<ErrorIcon /> {errors[field.name]}</div>
            )}
            {error && <FormFeedback>{error}</FormFeedback>}

        </FormGroup>
    )
}

Used it in my form like this. If I should change the to , it is a little bit faster.

<Field
                                            name="currentStatus"
                                            component={SyncRRSInput}
                                            options={attendance.map(x => {
                                                return {value: x.value, text: x.label}
                                              })}
                                            showLabel={true}
                                            label="Status"
                                            isRequired={true}
                                            
                                        /> 

Note: I made use of reactstrap.

@carlospence
Copy link
Author

On further Checking, I realized that the caretIcon property is the cause, If I remove the property, the component works very well but immediately I add it back, it becomes very slow even with using RRS directly on my form

@benbowes
Copy link
Owner

Which version are you on @carlospence ?

Also could you try caretIcon={"+"} to see if it is the caretIcon prop or the CaretIcon?

Also please try the next version of react-responsive-select which is react-responsive-select-next as this is slightly different

I will have a look and see if there is something unusual going on in the caretIcon prop

@carlospence
Copy link
Author

carlospence commented Jul 1, 2020

I used the latest version 5.1.1. I tried caretIcon={"+"} and worked well, it was not sluggish or slow, so I think it is the caretIcon prop.

I also tried the next version and had the same issue.

I believe it is the way the props are rendered or something. Even the is causing the same issue. If I ever use any JSX Element as prop for caretIcon and markup for multiselect. The response of all the components on my page will be very slow even the modal popup loading the component will be slow to show up. But whenever I remove all the JSX element as prop and use ordinary strings, every works smoothly.

Find attached two Gifs for the slowness or fastness.

Slow with caratIcon and markup
Slow Component With Prop

Fast without caratIcon and markup
Fast Component Without Prop

Using the next version with cartIcon and mark: Also slow

Soul Winning App - Google Chrome 7_1_2020 1_11_15 PM

@benbowes
Copy link
Owner

benbowes commented Jul 1, 2020

Thanks for all the details @carlospence 👍

This is interesting. I have not noticed any speed problems before with the caretIcon.

I am rendering whatever is in caretIcon like this https://github.com/benbowes/react-responsive-select/blob/master/src/components/SingleSelect.tsx#L82

e.g.

<div>
  {props.caretIcon && props.caretIcon}
</div

The same way I would render props.children

Is it possible that field.name is changing somehow? as in the key here <CaretIcon key={${field.name}-caret} />.

If it is not set or is changing, it might be causing the component to re-render multiple times.

@benbowes
Copy link
Owner

benbowes commented Jul 1, 2020

Please also try spreading the props on the <CaretIcon /> like this:

export const CaretIcon = (props: any): React.ReactElement => (
  <svg
    className="caret-icon"
    x="0px"
    y="0px"
    width="11.848px"
    height="6.338px"
    viewBox="351.584 2118.292 11.848 6.338"
    {...props}
  >
    <g>
      <path d="M363.311,2118.414c-0.164-0.163-0.429-0.163-0.592,0l-5.205,5.216l-5.215-5.216c-0.163-0.163-0.429-0.163-0.592,0s-0.163,0.429,0,0.592l5.501,5.501c0.082,0.082,0.184,0.123,0.296,0.123c0.103,0,0.215-0.041,0.296-0.123l5.501-5.501C363.474,2118.843,363.474,2118.577,363.311,2118.414L363.311,2118.414z" />
    </g>
  </svg>
);

Which should enable you to remove the key prop.

@benbowes
Copy link
Owner

benbowes commented Jul 1, 2020

This CaretIcon ☝️ is exactly the same as the existing CaretIcon in RRS, except for the spread props.

@carlospence
Copy link
Author

carlospence commented Jul 1, 2020

Thanks, I just added the spread props and there was a very good improvement on the single select. But on the multiple select with markup component. If I include the , there is still some form of sluggishness. I added the spread props on the MultiSelectOptionMarkup components. It didn't lead to any improvement.

Can you check out my Formik Component to see if anything could trigger re-rendering #148

And I called the component this way:

<FastField
                                            name="response"
                                            key="response"
                                            component={RRSMultiInput}
                                            // options={sowinOptions.prospectResponse.map(x => {
                                            //     return {value: x.value, text: x.label, markup: <MultiSelectOptionMarkup key={`${x.label}-response`}>{x.label}</MultiSelectOptionMarkup>}
                                            //   })}
                                              options={sowinOptions.prospectResponse.map(x => {
                                                return {value: x.value, text: x.label, markup: x.label}
                                              })}  
                                            placeholder="Feedback"
                                            showLabel={true}
                                            label="Feedback"
                                            hasSelectAll={false}
                                            isRequired={true}

                                        />

I toggled between options with MultiSelectOptionMarkup and without

@benbowes
Copy link
Owner

benbowes commented Jul 2, 2020

I am not sure if you have tried using these versions of components for the MultiSelectOptionsMarkup?
https://github.com/benbowes/react-responsive-select-next/blob/master/src/Extras.tsx#L44

But based on what you have discovered above, I think the spread props I have here, should be spread over the CheckboxIcon

export const MultiSelectOptionMarkup = ({ text, ...props }: { text: string; props: any }): React.ReactElement => (
  <div {...props}>
    <span className="checkbox">
      <CheckboxIcon />
    </span>
    <span> {text}</span>
  </div>
);

like this:

export const MultiSelectOptionMarkup = ({ text, ...props }: { text: string; props: any }): React.ReactElement => (
  <div>
    <span className="checkbox">
      <CheckboxIcon {...props} />
    </span>
    <span> {text}</span>
  </div>
);

@benbowes
Copy link
Owner

benbowes commented Jul 2, 2020

Really appreciate your help with this

@carlospence
Copy link
Author

@benbowes Not a problem @ALL. I really appreciate your prompt responses also and help so far.

There have been an appreciable improvement but not up to the speed of no caretIcon or no MultiSelectOptionMarkup. I even think the major snag is still with the MultiSelectOptionMarkup. Though there was slight improvement but the improvement is more obvious with single select than with multiselect.

However, using noSelectionLabel with multiselect cause the component to freeze when making selections. It works well with single select and the major reason I want to use this component is for its multiselect on mobile. I like the way it pops up like native select on mobile.

@benbowes
Copy link
Owner

@carlospence I haven't tried using Formik Field, but I can see that I need to put a multiselect example in the docs

Some thing like this in react-responsive select-next (soon to be react-responsive-select) would give some insight in to how to go:

import { Select, CaretIcon, ErrorIcon, MultiSelectOptionMarkup } from '../../react-responsive-select-next';
import { Formik, Field } from 'formik';
import * as Yup from 'yup';

const MyForm = () => (
  <Formik
    initialValues={{
      car: 'lexus',
      bikes: ['bmw'],
    }}
    onSubmit={(values, { setSubmitting }) => {
      setTimeout(() => {
        setSubmitting(false);
        alert(JSON.stringify(values, null, 2));
      }, 2000);
    }}
    validationSchema={Yup.object().shape({
      car: Yup.mixed().notOneOf(['null'], 'Please select a car'),
      bikes: Yup.mixed().test({
        name: 'something-other-than-any-selected',
        message: 'Please select a bike',
        test: value => value.indexOf('null') === -1,
      }),
    })}
  >
    {formikProps => (
      <form onSubmit={formikProps.handleSubmit}>
        <p>Trigger an error by selecting "Any"</p>
        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
          <div
            style={{ flex: '1 1 50%' }}
            className={formikProps.errors.car && formikProps.touched.car ? 'has-error' : ''}
          >
            <p>Single Select</p>
            <Select
              name="car"
              selectedValue={formikProps.values.car}
              caretIcon={<CaretIcon />}
              disabled={formikProps.isSubmitting}
              onSubmit={formikProps.handleSubmit}
              onChange={({ value, name }) => {
                formikProps.handleChange({ target: { value, name } });
              }}
              options={[
                { value: 'null', text: 'Any' },
                { value: 'alfa-romeo', text: 'Alfa Romeo' },
                { value: 'bmw', text: 'BMW' },
                { value: 'fiat', text: 'Fiat' },
                { value: 'lexus', text: 'Lexus' },
                { value: 'morgan', text: 'Morgan' },
                { value: 'subaru', text: 'Subaru' },
              ]}
            />
            {formikProps.errors.car && formikProps.touched.car && (
              <div className="field-error-message">
                <ErrorIcon /> {formikProps.errors.car}
              </div>
            )}
          </div>

          <div
            style={{ flex: '1 1 50%' }}
            className={formikProps.errors.bikes && formikProps.touched.bikes ? 'has-error' : ''}
          >
            <p>Multi Select</p>
            <Select
              multiselect={true}
              name="bikes"
              selectedValues={formikProps.values.bikes}
              caretIcon={<CaretIcon />}
              disabled={formikProps.isSubmitting}
              onSubmit={formikProps.handleSubmit}
              onChange={({ altered, options }) => {
                if (altered) {
                  formikProps.handleChange({ target: { value: options.map(option => option.value), name: 'bikes' } });
                }
              }}
              options={[
                {
                  value: 'null',
                  text: 'Any',
                  markup: <MultiSelectOptionMarkup text="Any" />,
                },
                {
                  value: 'bmw',
                  text: 'BMW',
                  markup: <MultiSelectOptionMarkup text="BMW" />,
                },
                {
                  value: 'honda',
                  text: 'Honda',
                  markup: <MultiSelectOptionMarkup text="Honda" />,
                },
                {
                  value: 'motoguzzi',
                  text: 'Moto Guzzi',
                  markup: <MultiSelectOptionMarkup text="Moto Guzzi" />,
                },
                {
                  value: 'suzuki',
                  text: 'Suzuki',
                  markup: <MultiSelectOptionMarkup text="Suzuki" />,
                },
                {
                  value: 'vespa',
                  text: 'Vespa',
                  markup: <MultiSelectOptionMarkup text="Vespa" />,
                },
              ]}
            />
            {formikProps.errors.bikes && formikProps.touched.bikes && (
              <div className="field-error-message">
                <ErrorIcon /> {formikProps.errors.bikes}
              </div>
            )}
          </div>
        </div>

        <br />

        <div>
          <button type="button" className="outline" onClick={formikProps.handleReset} disabled={formikProps.isSubmitting}>
            Reset
          </button>

          <button type="submit" disabled={formikProps.isSubmitting}>
            {formikProps.isSubmitting ? '...' : 'Submit'}
          </button>
        </div>
      </form>
    )}
  </Formik>
);

@benbowes
Copy link
Owner

@benbowes
Copy link
Owner

see #150

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

2 participants