logaretm / vee-validate

✅ Painless Vue forms
https://vee-validate.logaretm.com/v4
MIT License
10.7k stars 1.25k forks source link

When an input field updates it re-renders every single other form input. Terrible performance for color-pickers. #3266

Closed SanderCokart closed 2 years ago

SanderCokart commented 3 years ago

Versions

When an input field updates it re-renders every single other form input. Terrible performance for color-pickers.

To reproduce

Steps to reproduce the behavior: Create a vee-validate Form component and put multiple custom input field components inside it that makes use of the useField hook from vee-validate like this and return the inputValue and errorMessage to the template, place the errorMessage anywhere and use inputValue on the v-model of the input.

example script

<script>
import { useField } from 'vee-validate';

export default {
  name: 'FormColor',
  props: {
    value: {
      type: String,
      default: '#000000',
    },
  },
  setup (props) {
    /* HOOKS */
    const { value: inputValue, errorMessage } = useField(props.name, props.rules, {
      initialValue: props.value,
    });

    /* RETURNED TO TEMPLATE */
    return { inputValue, errorMessage };
  },
};

</script>

example template

<template>
  {{ inputValue }}
  <input :id="name"
         v-model="inputValue"
         :name="name"
         type="color">
  <span class="error">{{ errorMessage }}</span>
</template> 

add :last-render="Date.now()" to any of those input field components. In vue dev-tools check for the component with that prop. You will see a timestamp that updates regardless of which input component is updated. If 1 updates, all of them update and re-rerender.

Expected behavior when 1 inputfield get changed it only re-renders that component and not every component consuming useField.

Try it via codesandbox, you do have to copy it to a local dev environment as dev-tools cannot detect vue in sandbox https://codesandbox.io/s/vee-validate-example-6poyh?file=/src/components/FormColor.vue

logaretm commented 3 years ago

What

I have checked this and here are my findings:

The re-rendering is not caused by the useField, it's caused by the Form component scoped slot.

Optimization for this can be implemented, but won't prevent re-rendering in many cases. Basically, even if I optimized the re-rendering of the Form component it will still re-render if you use anything reactive that changes with field interactions.

<!-- Won't re-render -->
<Form>
  <!-- Some Fields -->
</Form>

<!-- Will re-render no matter what -->
<Form v-slot="{ values }">
  <!-- Some Fields -->

  {{ values }}
</Form>

This makes sense as when the slot scope changes the function for the slot scope is called again and then you would have the date value change as with your case.

Having said that, optimization has very little value as I mentioned, the form will re-render if you use anything reactive from the slot props.

This includes errors, values, and meta which are common to use with the slot scope, and since they always change with the interaction of fields, that will make re-rendering very common.


Workarounds

You could use useForm instead to construct your forms and it won't cause re-rendering because it doesn't make any assumptions about your template, unlike Form which has to pass everything reactive to the slot props. You will basically be in control of what reactive properties to use and how to avoid re-rendering yourself.

Another worthwhile consideration is you should debounce your inputs such as that one, the update frequency is too much anyways and validations will trigger needlessly, I have implemented a few color picker inputs with vee-validate and I always denounce their handlers while keeping the visuals snappy.

I won't close this just yet as I would like to take another stab at it and see if I can find a good middle-ground.

logaretm commented 2 years ago

4.5 was released with a couple of performance enhancements:

This doesn't directly affect this issue but since the form API is available as composition API and would give the control needed to optimize this on the app level I will be closing this.