Reset doesn't update the value prop on a custom component
Describe the bug
I have a custom input field which uses the same logic for the input field to handle the label as: https://smeltejs.com/components/text-fields
The issue that I have is, that when I call the reset function on the context, it will reset the value, but the value prop on the component itself is not changed. The component needs to react on value changes, to set the label back. But instead the label is still minimized at the top of the field. It's a little bit strange, because the value itself is reset in the text field. The problem is only that the value prop gets not updated.
A workaround is to use the form data directly and bind it to the value of the field:
const { form: passwordForm, data: passwordData } = createForm<PasswordForm>({
...
});
$: password = getValue($passwordData, (d) => d.password);
<form use:passwordForm>
<PasswordField
id="password"
name="password"
label={$LL.Form.Fields.Password()}
value={password}
/>
</form>
With this the value prop on the input component gets updated.
Which package/s are you using?
felte (Svelte), @felte/reporter-svelte, @felte/validator-yup
Environment
- OS: MacOs
- Browser: Brave
- Version: 1.2.6
To reproduce
No response
Small reproduction example
No response
Screenshots
No response
Additional context
No response
Hey! When you say "custom component" do you mean something built with createField? If so, it does accept an onFormReset function to handle situations like this.
Hi, with custom component I mean a svelte component like:
<script lang="ts">
import { fade } from 'svelte/transition';
import { ValidationMessage } from '@felte/reporter-svelte';
import { AlertCircle } from 'lucide-svelte';
export let id: string;
export let name: string;
export let type = 'text';
export let label: string;
export let value = null;
export let maxlength: number = undefined;
export let disabled = false;
let focused = false;
function toggleFocused() {
focused = !focused;
}
// https://stackoverflow.com/a/57393751/2153190
const handleInput = (e) => {
// in here, you can switch on type and implement
// whatever behaviour you need
value = type.match(/^(number|range)$/) ? +e.target.value : e.target.value;
};
$: labelOnTop = focused || value || value === 0;
</script>
<div class="flex relative w-full h-12">
<input
aria-label={label}
on:focus={toggleFocused}
on:blur={toggleFocused}
on:input={handleInput}
{id}
{name}
{type}
{maxlength}
{disabled}
{value}
class="w-full input input-bordered focus:outline-none pt-4"
class:pr-12={$$slots.rightIcon != null}
/>
<label
for={id}
class="label absolute left-3 top-1 right-3 cursor-text pointer-events-none label-transition text-base-content/70"
class:label-top={labelOnTop}
class:text-xs={labelOnTop}
>
<span class:truncate={!labelOnTop}>{label}</span>
</label>
{#if $$slots.rightIcon != null}
<div class="absolute right-0 h-12 w-12"><slot name="rightIcon" /></div>
{/if}
</div>
<ValidationMessage for={id} let:messages={message}>
{#if message}
<div transition:fade|local class="flex text-error text-sm">
<span class="pt-[0.4rem] pr-1"><AlertCircle size={15} /></span>
<p class="pt-1">{message}</p>
</div>
{/if}
</ValidationMessage>
<style>
/* Disable background color added by WebKit for autofilled fields */
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition: background-color 600000s 0s, color 600000s 0s;
}
/*
Override default style for hidden fields so that they have the same background as the normal field.
The default DaisyUI style uses the base-200 style. To distinguish between the normal field we use a
border that has a opacity of 0.1 instead of 0.2. The font color uses also an opacity of 0.6.
*/
input[disabled] {
--tw-border-opacity: 0.1;
--tw-bg-opacity: 1;
--tw-text-opacity: 0.6;
border-color: hsl(var(--bc, var(--bc)) / var(--tw-border-opacity));
background-color: hsl(var(--b1, var(--b1)) / var(--tw-bg-opacity));
color: hsl(var(--bc, var(--bc)) / var(--tw-text-opacity));
}
.label-top {
line-height: 0.05;
}
.label-transition {
transition: font-size 0.05s, line-height 0.1s;
}
:global(label.text-xs) {
font-size: 0.7rem;
}
</style>
The form is created with the createForm helper.
Ah. I think I see where the issue comes from. Yeah, while Felte does reset the value of the input itself (in HTML), browser's don't dispatch any event when a value changes programatically. So there's no way for Svelte to know the value has changed.
Assigning the value as you mentioned before works, but for ergonomics maybe creating the component with createField and implementing onFormReset solves your issue?
I tried your suggestion, but it doesn't work. Maybe I have done something wrong.
Here is the update component with createField:
<script lang="ts">
import { fade } from 'svelte/transition';
import { ValidationMessage } from '@felte/reporter-svelte';
import { AlertCircle } from 'lucide-svelte';
import { createField } from 'felte';
export let id: string;
export let name: string;
export let type = 'text';
export let label: string;
export let value = null;
export let maxlength: number = undefined;
export let disabled = false;
let focused = false;
const { field, onInput, onBlur } = createField(name, {
onFormReset: () => {
console.log('onFormReset');
focused = false;
}
});
function toggleFocused() {
focused = !focused;
}
// https://stackoverflow.com/a/57393751/2153190
const handleInput = (e) => {
// in here, you can switch on type and implement
// whatever behaviour you need
value = type.match(/^(number|range)$/) ? +e.target.value : e.target.value;
onInput(value);
};
const handleBlur = () => {
toggleFocused();
onBlur();
};
$: labelOnTop = focused || value || value === 0;
</script>
<div class="flex relative w-full h-12">
<input
use:field
on:focus={toggleFocused}
on:blur={handleBlur}
on:input={handleInput}
aria-label={label}
{id}
{name}
{type}
{maxlength}
{disabled}
{value}
class="w-full input input-bordered focus:outline-none pt-4"
class:pr-12={$$slots.rightIcon != null}
/>
<label
for={id}
class="label absolute left-3 top-1 right-3 cursor-text pointer-events-none label-transition text-base-content/70"
class:label-top={labelOnTop}
class:text-xs={labelOnTop}
>
<span class:truncate={!labelOnTop}>{label}</span>
</label>
{#if $$slots.rightIcon != null}
<div class="absolute right-0 h-12 w-12"><slot name="rightIcon" /></div>
{/if}
</div>
<ValidationMessage for={id} let:messages={message}>
{#if message}
<div transition:fade|local class="flex text-error text-sm">
<span class="pt-[0.4rem] pr-1"><AlertCircle size={15} /></span>
<p class="pt-1">{message}</p>
</div>
{/if}
</ValidationMessage>
<style>
/* Disable background color added by WebKit for autofilled fields */
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition: background-color 600000s 0s, color 600000s 0s;
}
/*
Override default style for hidden fields so that they have the same background as the normal field.
The default DaisyUI style uses the base-200 style. To distinguish between the normal field we use a
border that has a opacity of 0.1 instead of 0.2. The font color uses also an opacity of 0.6.
*/
input[disabled] {
--tw-border-opacity: 0.1;
--tw-bg-opacity: 1;
--tw-text-opacity: 0.6;
border-color: hsl(var(--bc, var(--bc)) / var(--tw-border-opacity));
background-color: hsl(var(--b1, var(--b1)) / var(--tw-bg-opacity));
color: hsl(var(--bc, var(--bc)) / var(--tw-text-opacity));
}
.label-top {
line-height: 0.05;
}
.label-transition {
transition: font-size 0.05s, line-height 0.1s;
}
:global(label.text-xs) {
font-size: 0.7rem;
}
</style>
With this code I get the following error:
create-field.ts:71 Uncaught RangeError: Maximum call stack size exceeded.
at dispatchEvent (create-field.ts:71:13)
at onInput (create-field.ts:144:5)
at HTMLInputElement.handleInput (InputField.svelte:34:9)
at dispatchEvent (create-field.ts:71:13)
at onInput (create-field.ts:144:5)
at HTMLInputElement.handleInput (InputField.svelte:34:9)
at dispatchEvent (create-field.ts:71:13)
at onInput (create-field.ts:144:5)
at HTMLInputElement.handleInput (InputField.svelte:34:9)
at dispatchEvent (create-field.ts:71:13)
I can fix that by removing onInput(value);. But after removing the part, the onFormReset function will not be called.
Do you have an idea?