I am working on creating a multi-step form using Svelte.js but I've run into an issue rendering each form page with unique props.
Here is a simple demo to show you what I mean:
// App.svelte
<script>
import FormPage from "./FormPage.svelte";
let formNode;
let pageSelected = 0;
let formPages = [
{
name: "email",
label: "Email"
},
{
name: "password",
label: "Password"
}
];
const handleIncPage = () => {
if(pageSelected + 1 < formPages.length)
pageSelected = pageSelected + 1;
}
const handleDecPage = () => {
if(pageSelected -1 > -1)
pageSelected = pageSelected - 1;
}
</script>
<form bind:this={formNode}>
<FormPage pageData={formPages[pageSelected]} />
</form>
<button on:click={handleDecPage}>Back</button>
<button on:click={handleIncPage}>Next</button>
<p>
Page Selected: {pageSelected}
</p>
And here's the FormPage
component:
// FormPage.svelte
<script>
export let pageData;
const {name, label} = pageData;
</script>
<div id={`form-page-${name}`}>
<label>Label: {label}</label>
<input type="text" name={name} id={`input-${name}`} />
</div>
<pre>{JSON.stringify(pageData, null, 2)}</pre>
When I run the application and inc/dec pageSelected
, the pageData
prop changes successfully - as can be seen in the pre
element. However, the label
and input
elements are exactly the same as they are on the first page. The id
of the wrapping div
element and the id
of the input
element are also unchanged.
When I type into the input
and change the page, the text remains the same.
My goals are: to rerender the FormPage
component when pageSelected
changes and have the input
and label
change their values based on these new props. It should also be the case that when I change pages, the text already typed into the input
should update and be empty (since no initial value is given to the inputs).
Having done multi-step forms in React.js, I would use a unique key
attribute to make sure my FormPage
rerenders every time the pageSelected
state changes. But I am unsure of how to do something similar in Svelte.js.
Any suggestions?
UPDATE:
Having read this question on StackOverflow, I found out how to get the input
and label
elements to change as well as the id
attributes. Here is my updated FormPage
component:
// FormPage.svelte
<script>
export let pageData;
$: name = pageData.name;
$: label = pageData.label;
</script>
<div id={`form-page-${name}`}>
<label>Label: {label}</label>
<input type="text" name={name} id={`input-${name}`} />
</div>
<pre>{JSON.stringify(pageData, null, 2)}</pre>
However, the text inside the input
still does not change.
Is there a way to also update the value of the input as well? It would be ideal for my use case to create an entirely new element every time the props change, but it appears that Svelte is only updating the few attributes that have changed on the same underlying DOM node.
Can Svelte be told to recreate elements in this circumstance?
UPDATE 2
So I have figured out a way to get the value of the input to change. Here is my updated FormPage
component:
// FormPage.svelte
<script>
export let pageData;
import {onMount, afterUpdate, onDestroy} from "svelte";
let inputNode;
$: name = pageData.name;
$: label = pageData.label;
$: value = pageData.initialValue || "";
onMount(() => {
console.log("Mounted");
});
afterUpdate(() => {
console.log("Updated");
inputNode.value = value
});
onDestroy(() => {
console.log("Destroyed");
});
</script>
<div id={`form-page-${name}`}>
<label>Label: {label}</label>
<input type="text" name={name} id={`input-${name}`} bind:this={inputNode} />
</div>
<pre>{JSON.stringify(pageData, null, 2)}</pre>
This solves the immediate problem of updating the input value.
I've added the onMount
, afterUpdate
and onDestroy
functions to see how the component changes over time.
First, the onDestroy
function is called, and then onMount
after that. These both fire only once. However, afterUpdate
fires every time the props change. This confirms my suspicion that the component wasn't being recreated.