I have a simple text input that looks like this
// App.svelte
<script>
import { todo, todos, count, addTodo, error } from "./stores";
</script>
<div>
<input type="text" bind:value={$todo.text} />
{#if $error}
<span >{$error}</span>
{/if}
<button on:click={addTodo}>Add Todo</button>
<!-- show todo items -->
</div>
Everything is abstracted out neatly into a store which looks like this:
import { writable, derived, get } from "svelte/store";
export const todo = writable({ text: "", completed: false });
export const todos = writable([{ text: "Learn Svelte", completed: false }]);
const formState = writable({ status: "idle" });
export const addTodo = () => {
formState.set({ status: "submitted" });
let _error = get(error);
if (!_error) {
let _todo = get(todo);
todos.update((todos) => {
todos.push(_todo);
todo.update(() => ({ text: "", completed: false }));
return todos;
});
formState.set({ status: "idle" });
}
};
...
export const error = derived(todo, todo => {
if (get(formState).status === "submitted") {
if (!todo.text.trim()) {
return "Please enter a task";
} else if (todo.text.length < 3) {
return "Todo must be at least 3 characters long";
}
return "";
}
});
I'm sure there's better ways to do form validation but the purpose of this example is
to play around with Svelte stores. I have created an error
derived store that does some validation once the form is submitted by subscribing to the todo
value. When I click the 'Add Todo' button initially, I get the correct error, but an empty todo is rendered (and apparently the error is undefined), then the form validation kicks in subsequently. Why is this?
Codesandbox - https://codesandbox.io/s/stupefied-chaplygin-vzvg7e
CodePudding user response:
Your derived store is implemented incorrectly: It does not list all stores it depends on.
If you ever use get
you are losing reactivity, so that can be a sign something is wrong. In this case the formState
needs to be included, otherwise the store will not update if that state changes:
export const error = derived([todo, formState], ([$todo, $formState]) => {
if ($formState.status === "submitted") {
if (!$todo.text.trim()) {
return "Please enter a task";
} else if ($todo.text.length < 3) {
return "Todo must be at least 3 characters long";
}
return "";
}
});
(Would recommend the $
prefix convention to not shadow variables and indicate that something is a store value, rather than the store itself.)