Skip to content

Commit

Permalink
updated bug examples for the b after a bug
Browse files Browse the repository at this point in the history
  • Loading branch information
joepuzzo committed May 9, 2024
1 parent addbaba commit ce8ce12
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 115 deletions.
141 changes: 74 additions & 67 deletions vitedocs/Pages/Examples/AfterRenderBug/AfterRenderBug.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,109 @@ import Code from '../../../YourComponents/Code';
import { SideBySide } from '../../../SideBySide';
import Example from './Example';
import exampleCode from './Example.jsx?raw';
import EffectOrder from './EffectOrder';
import effectOrderCode from './EffectOrder.jsx?raw';
import { Info } from '../../../Info';

export default function AfterRenderBug() {
return (
<>
<h1>After Relevance Bug</h1>
<h1>After Render Bug</h1>
<Info>
The issue occured when a field “a” validates using a field “b” where
field “b” is rendered after field “a”. There are three scenarios that
are of concern here.
</Info>

<Info type="notice">
1. validateOnMount IS passed in, the form will validate for the first
<code>Scenario1:</code>
<Code>
{`// validateOnMount is passed to field "a"
<Input name="a" label="A" validate={validate} showErrorIfError validateOnMount />
<Input name="b" label="B" />`}
</Code>
<br />
validateOnMount IS passed in, the form will validate for the first
profile but not the second. This is because when we validate "OnReset"
field "b" does not have a value yet. This is an inconsistancy due to the
fact that there is a difference between first render and reset. On first
render there is a "double" initialization, one that happens before
render, and one that occurs after. The purpose of this was to ensure
things ( such as validation ) got triggered once all fields were
rendered. When the user changes to the second profile, a reset will get
triggered on all fields, validateOnMount still forces the field to
validate, however at the time of validation for field "a", field "b" has
not yet been set back to its new value. The solution to this was to
break up the form "reset" function into two parts, where we first
itterate and initialize the values and the second goes back and
re-validates each field! ( only if validateOnMount was passed to that
field)
</Info>
<Info type="notice">
2. validateOnMount is NOT passed but validateWhen IS. Example
{` validateWhen=['b']`} This resulted in the form performing NO
validation for field "a" when it was first rendered, however when
choosing a new profile it would perform validation, this is obviously
due do the fact that when a reset occurs, a value change occurs for
field "b" as it goes from "World" to "Elon". This triggers the internal
subscription that gets created when passing {` validateWhen=['b']`} to
field "a". To fix this we could update the code to emit an event in the
second initialization because truly the value is changing. However this
causes an entire new issue. Now this would, for example, trigger
validation on a field that is required but we have no intention of
showing the user this required error on mount. If we make this change,
while it would fix the issue described, it would cause another. For now
we may accept that there is truly a difference between the initial mount
and a reset in the context of a validateWhen.
render, and one that occurs after.
<br />
<br />
The purpose of this was to ensure things ( such as validation ) got
triggered once all fields were rendered.
<br />
<br />
When the user changes to the second profile, a reset will get triggered
on all fields, validateOnMount still forces the field to validate,
however at the time of validation for field "a", field "b" has not yet
been set back to its new value.
<br />
<br />
The solution to this was to break up the form "reset" function into two
parts, where we first itterate and initialize the values and the second
goes back and re-validates each field! ( only if validateOnMount was
passed to that field )<br />
<br />
<strong>Bug - Fixed in v4.60.0</strong>
</Info>
<Info type="notice">
3. validateOnMount IS passed and also validateWhen IS passed. Example
{` validateWhen=['b'] validateOnMount`}. This had a bug because, while
in theory it would not encounter an issue as it should validate no
matter what due to the issue descirbed in 2, it in fact has an issue in
array fields... Why? ... Because array fields themselves have a special
reset function. Similar to how leaf nodes ( normal fields ) register
with the form controller so do arrayFields. Because array fields
maintain a state of keys, which are used to optimize how array fields
work internally, a reset will trigger a clear of these keys and a new
one will be built. Note, this is similar to how the FormController will
reset the entire form state before calling reset on all registered
fields. The issue was because it would
<code>Scenario2:</code>
<Code>
{`// validateOnMount is NOT passed but validateWhen IS
<Input name="a" label="A" validate={validate} showErrorIfError validateWhen={['b']} />
<Input name="b" label="B" />`}
</Code>
<br />
This results in the form performing NO validation for field "a" when it
was first rendered, however when choosing a new profile it would perform
validation.
<br />
1: Initialize stuff[0].b
<br />
This is obviously due do the fact that when a reset occurs, a value
change occurs for field "b" as it goes from "World" to "Elon" ( see
example below ).
<br />
2: CLEANUP REMOVING stuff[0].b
<br />
This triggers the internal subscription that gets created when passing{' '}
{` validateWhen=['b']`} to field "a". To fix this we could update the
code to emit an event in the second initialization because truly the
value is changing. However this causes an entire new issue. Now this
would, for example, trigger validation on a field that is required but
we have no intention of showing the user this required error on mount.
If we make this change, while it would fix the issue described, it would
cause another. For now we may accept that there is truly a difference
between the initial mount and a reset in the context of a validateWhen.
<br />
3: Second Initialize stuff[0].a but now B value is gone do too cleanup
<br />
<strong>Expected Behavior - Nothing to fix</strong>
</Info>
<Info type="notice">
<code>Scenario3:</code>
<Code>
{`// both validateOnMount, and validateWhen are passed in
<Input name="a" label="A" validate={validate} showErrorIfError validateOnMount validateWhen={['b']} />
<Input name="b" label="B" />`}
</Code>
<br />
This had a bug because, while in theory it would not encounter an issue
as it should validate no matter what due to the issue descirbed in 2, it
in fact has an issue in array fields...
<br />
<br />
Why? ... Because array fields themselves have a special reset function.
Similar to how leaf nodes ( normal fields ) register with the form
controller so do arrayFields.
<br />
<br />
The question here is why is the cleanup occuring after the initialize...
it was because after resetting the keys of the array field it renders
new items and after beginning to show the new items the old items will
start to clean themselves up. The issue here is because the names are
the same, for example {`Initialize stuff[0].b`} and{' '}
{`DEREGISTER stuff[0].a`}, the order these occurs matters as it will set
the new value to "Elon" but then the cleanup function will blow it away!
Technically it comes back due to our "SecondInitialize" call but thats
to little too late because at the time a validates ( on second
initialize ) b wont have its value. The fix described in 2 would also
fixed this issue.
Because array fields maintain a state of keys, which are used to
optimize how array fields work internally, a reset will trigger a clear
of these keys and a new one will be built. Note, this is similar to how
the FormController will reset the entire form state before calling reset
on all registered fields. In order to go into more details on this issue
please visit the array version of this bug in the docs :)
<br />
<br />
NOTE: To understand this probelm better see the "Effect Order" example
below
<strong>Bug - Fixed in v4.60.0</strong>
</Info>
<h2> Example Scneario 1</h2>
<SideBySide
Expand All @@ -98,14 +113,6 @@ export default function AfterRenderBug() {
left={<Example />}
right={<Code links input1={exampleCode} />}
/>
<hr />
<h2>Understanding Effect Order</h2>
<SideBySide
leftHeader={<h3>Example: </h3>}
rightHeader={<h3>Code:</h3>}
left={<EffectOrder />}
right={<Code links input1={effectOrderCode} />}
/>
</>
);
}
118 changes: 70 additions & 48 deletions vitedocs/Pages/Examples/AfterRenderBugArray/AfterRenderBugArray.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { SideBySide } from '../../../SideBySide';
import Example from './Example';
import exampleCode from './Example.jsx?raw';
import { Info } from '../../../Info';
import EffectOrder from './EffectOrder';
import effectOrderCode from './EffectOrder.jsx?raw';

export default function AfterRenderBugArray() {
return (
Expand All @@ -13,65 +15,85 @@ export default function AfterRenderBugArray() {
</Info>
<Info type="notice">
This issue occured when we had validateOnMount and also validateWhen
passed to inputs within an array field, specifically where field "a"
validates when field "b" changes and field "b" gets rendered after field
"a". The behavior observed would be, when changing profiles and
therefore triggering a form reset, the validateOnMount would trigger a
validation after the "Second Initialize" of field "a". But due to the
lifecycle of react. Because array fields maintain a state of keys, which
are used to optimize how array fields work internally, a reset will
trigger a clear of these keys and a new one will be built. Note, this is
similar to how the FormController will reset the entire form state
before calling reset on all registered fields. The issue was because it
would
<br />
<br />
1: Initialize stuff[0].b ( due to setting a new set of keys )<br />
<br />
2: CLEANUP REMOVING stuff[0].b ( due to old inputs cleaning up )<br />
<br />
3: Second Initialize stuff[0].a but now B value is gone do too cleanup
<br />
<br />
The question here is why is the cleanup occuring after the initialize...
it was because after resetting the keys of the array field it renders
new items and after beginning to show the new items the old items will
start to clean themselves up. The issue here is because the names are
the same, for example {`Initialize stuff[0].b`} and{' '}
{`DEREGISTER stuff[0].b`}, the order these occurs matters as it will set
the new value to "Elon" but then the cleanup function will blow it away!
Technically it comes back due to our "SecondInitialize" call but thats
to little too late because at the time a validates ( on second
initialize ) b wont have its value. Why?.. becuase after the first
initialize, the old "b" will clean himself up, removing the newly
initialized value "Elon", for field "b". Because at the time of second
initialization for "a" the value for "b" is undefiend it will error out,
even tho its not supposed to be in an error state! The solution to this
was a bit interesting but seems to have worked! Basically Internally I
wait until all fields part of this array field finish cleaning up after
themselves, only then do I set the new set of keys which will begin the
process of re-rendering the new inputs :)
</Info>
<Info>
<br />A few things to note:
passed to inputs within an array field.
<br />
<br />
Specifically where field "a" validates when field "b" changes and field
"b" gets rendered after field "a".
<Code>
{`// both validateOnMount, and validateWhen are passed in
<Input name="a" label="A" validate={validate} showErrorIfError validateOnMount validateWhen={['b']} />
<Input name="b" label="B" />`}
</Code>
<br />
The behavior observed would occur when changing profiles and therefore
triggering a form reset.
<br />
<br />
The validateOnMount would trigger a validation after the "Second
Initialize" of field "a". But due to the lifecycle of react. Because
array fields maintain a state of keys, which are used to optimize how
array fields work internally, a reset would trigger a clear of these
keys and a new one will be built.
<br />
<br />
So basically what would occur would be the following:
<br />
<br />
1: The array field <code>stuff</code> would set a new array of keys,
causing new fields to start to render. Remember, interally we call
initialize in a layoutEffect which occurs <strong>BEFORE</strong> any{' '}
<code>useEffect</code> cleanups.
<Code>{`// ==> Initialize stuff[0].b ... new value of "Elon" set`}</Code>
<br />
2: Now, the old field <code>"b"</code> would start to clean itself up,{' '}
<strong>REMOVING</strong> the value that just got initialized.
<Code>{`// ==> CLEANUP REMOVING stuff[0].b ... new value of "Elon" removed!`}</Code>
<br />
1. Resetting an array field will technically result in a removel of all
children fields, therefore we updated array field to deregister + remove
all records of these fields.
3: Finally the second initialize would trigger for field{' '}
<code>"a"</code> but now B value is gone do to the cleanup that occured
<Code>{`// ==> Second Initialize stuff[0].a ... validates with a value of undefined for field "b"`}</Code>
<br />
The order these occurs matters as it will set the new value to "Elon"
but then the cleanup function will blow it away! Technically it comes
back due to our "SecondInitialize" call but thats to little too late
because at the time <code>"a"</code> validates ( on second initialize )
<code>"b"</code> wont have its value.
<br />
2. This bug can more clearly be seen with this example as we error out
IF there is NO value for "b". Watch as you change from profile1 to
profile2, field A will error out! This is due to the issue described
above.
<br />
The solution to this was a bit interesting but seems to have worked!
Basically Internally I wait until all fields part of an array field, in
this case <code>"stuff"</code>
finish cleaning up after themselves, only then do I set the new set of
keys which will begin the process of re-rendering the new inputs :)
<br />
<br />
<strong>Bug - Fixed in v4.60.0</strong>
</Info>
<Info>
Note: This bug could more clearly be seen with this example as we error
out IF there is NO value for "b". Prior to this fix, as you change from
profile1 to profile2, field A would error out! This is due to the issue
described above.
</Info>
<Info type="notice">
Note: To better undertand reacts lifecylce please open inspector and
view the example below labeled <code>Understanding Effect Order</code>
</Info>
<SideBySide
leftHeader={<h3>Example: </h3>}
rightHeader={<h3>Code:</h3>}
left={<Example />}
right={<Code links input1={exampleCode} />}
/>
<hr />
<h2>Understanding Effect Order</h2>
<SideBySide
leftHeader={<h3>Example: </h3>}
rightHeader={<h3>Code:</h3>}
left={<EffectOrder />}
right={<Code links input1={effectOrderCode} />}
/>
</>
);
}

0 comments on commit ce8ce12

Please sign in to comment.