From dde08a5c887305eeeb70e05d1401fd6e9c918e88 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Fri, 29 Sep 2023 14:12:56 +0200 Subject: [PATCH] :bug: [open-formulieren/open-forms#3511] Fix flickering inputs on Webkit browsers Some difference in the JS engine in WebKit (the browser engine behind Safari or any other browser on iOS/MacOS) vs. Gecko (Firefox) and Blink (Chromium, Edge) seems to cause a different (debounce) behaviour and or event firing/acting on events w/r to the Formio renderer. On Webkit, the formio initialized event triggered with mismatching backend data, causing the form field inputs to be cleared and the submission to be updated, which in turn triggers the logic check again due to a change event being fired. This then leads again to a change in the formio configuration and the formio renderer re-initializing, ad infinitum. Somewhere the form data and form configuration state goes out of sync and it's incredibly hard to manage this, especially due to the large number of related issues in the past. Since we use the formio initialized event pretty much only to form step data from earlier submissions or when navigating between steps, we avoid setting updated form data at all in formio initialized events triggered by logic evaluations. The logic evaluation itself already manages the form instance submission data, in case that the backend returns calculated/derived values, for example. I truly hope this doesn't break anything else, but due to the lack of tests (granted, it's near impossible to test this mess) and complexity of this part of the code base, I can't say I'm very confident in this fix. --- src/components/FormStep/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/FormStep/index.js b/src/components/FormStep/index.js index 38c9b9f4c..cf1f0bfc7 100644 --- a/src/components/FormStep/index.js +++ b/src/components/FormStep/index.js @@ -180,6 +180,7 @@ class AbortedLogicCheck extends Error { const initialState = { configuration: null, backendData: {}, + formioInitialized: false, canSubmit: false, logicChecking: false, isFormSaveModalOpen: false, @@ -212,6 +213,7 @@ const reducer = (draft, action) => { } = action.payload; draft.configuration = configuration; draft.backendData = data; + draft.formioInitialized = false; draft.canSubmit = canSubmit; draft.logicChecking = false; draft.isNavigating = false; @@ -223,6 +225,11 @@ const reducer = (draft, action) => { break; } + case 'FORMIO_INITIALIZED': { + draft.formioInitialized = true; + break; + } + case 'BLOCK_SUBMISSION': { const {logicChecking = false} = action.payload || {}; draft.canSubmit = false; @@ -305,6 +312,7 @@ const FormStep = ({ { configuration, backendData, + formioInitialized, canSubmit, logicChecking, isFormSaveModalOpen, @@ -708,6 +716,14 @@ const FormStep = ({ return; } + // formio initialized also fires when the formio configuration changes bceause of + // logic, but we only tap into this hook to set the backend-loaded submission data. + // Once this is done, we don't want to run anything anymore. + // Otherwise this causes problems wich change events triggering and "bouncing" back + // and forth (in WebKit browsers) with the logic check via the onFormIOChange, see + // open-formulieren/open-forms#3511. + if (formioInitialized) return; + // We cannot filter 'blank' values to prevent Formio validation from running, as // Formio will use the default values in that case which have been explicitly // unset. In the situation that we have invalid backend data (loading a submission @@ -722,6 +738,7 @@ const FormStep = ({ // for FormIO (multivalue input is one example why that's needed). formInstance.setSubmission({data: cloneDeep(backendData)}, {noValidate: true}); } + dispatch({type: 'FORMIO_INITIALIZED'}); }; /**