My name is Pablo Berganza, I am a software developer, mostly working as a full-stack web developer.
My main programming language is JavaScript, usually using TypeScript.
I am currently maintaining two self-made open source projects:
felte: A form management library for Svelte, Solid and React.
svelte-markdown: A markdown renderer that renders to Svelte components.
uvu-expect: An assertion library with a similar syntax to ChaiJS aimed to be used with uvu.
uvu-expect-dom: A plugin for uvu-expect that adds assertions for the DOM. Basically a wrapper on top of @testing-library/jest-dom.
We are rendering a custom tree view inside a form. The tree nodes are expandable and contain input fields (checkboxes) with custom logic. When expanding the children of a node (with the corresponding checkboxes) the following error occured:
Uncaught Error: Function called outside component initialization
Here is the diff that solved my problem:
diff --git a/node_modules/@felte/reporter-svelte/src/reporter.js b/node_modules/@felte/reporter-svelte/src/reporter.js
index 8c7aa8f..82b2aa7 100644
--- a/node_modules/@felte/reporter-svelte/src/reporter.js+++ b/node_modules/@felte/reporter-svelte/src/reporter.js@@ -7,7 +7,6 @@ import { getPath, _get } from '@felte/common';
* @param {any} currentForm
*/
export function svelteReporter(currentForm) {
- if (!hasContext(formKey)) setContext(formKey, currentForm.errors);
if (!currentForm.form) return;
const unsubscribe = currentForm.errors.subscribe(($errors) => {
for (const control of currentForm.controls) {
just played with felte and had to ask myself the question if there is any reason I don't see, why there is no validator-cvapi?
Imho it could be lightweight validator for simple forms (like login f.e.) with built-in i18n. Most of the shortcomings of the cvapi are already handled by felte (premature validation, a11y-problems with the bubbles on mobile etc). So, am I overlooking something important or is it just not written yet? :)
Also while at it, I had a quick look on the api and noticed that the form itself isn't passed to the validate()-function - which would make the implementation of such an validator quite a bit harder. Therefor, additional question, any reasons for that? Maybe it could be a nice addition for extensibility to pass the form as a second arg I think!
And, last but not least: Thanks again for this great, well built, library! 💯
thank you for the great library, I was wondering if there are any examples or if it is actually supported to use promise based schemas with validator-zod?
When running npm i -D zod, it installs [email protected]. I tested ^3.0.0 as the peer dep and it works.
Currently, I hit an error due to version mismatch:
Found: [email protected]
npm ERR! node_modules/zod
npm ERR! dev zod@"^3.0.0-alpha.33" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer zod@"^1.11.13" from @felte/[email protected]
(I also noticed my vite error was resolved by removing zod and re-install using npm i -D zod as a devDependency. So we might want to update https://felte.dev/docs#using-zod)
I could add the index accessor to the interface (they are generated based on our API), but this would reduce the type safety when using keyof or similar expressions in TypeScript.
At the moment, the only argument to onSubmit() is the Data entered to the form as a simple object.
I would like to be able to do something like the following to abstract the onSubmit-handling in different contexts:
const{ form }=createForm<FormData>({onSubmit: async(values,form)=>{constformData=newFormData();for(constkeyofObject.keys(values)){formData.append(key,values[key]);}constresult=awaitfetch(form.action,{// <-- dynamic use of form.actionmethod: form.method,// <-- dynamic use of form.methodheaders: {accept: 'application/json'},body: formData}).then((res)=>res.json());// ...}});
Therefor it would be great, if the form-object could be the second argument to onSubmit().
Very nice job on felte! it's such an interesting form framework, and i love the approach of essentially defining everything from the HTML. I was thinking about how we usually handle the same form being used for multiple purposes in plain html, and it's done using the formActionattribute on the submit input, that way you can have several different submit buttons posting the form to different places.
I was thinking felte would be perfect for implementing a way to do this as well.
We could add another property data-felte-set-on-submit to input fields, that would only include the value of that field, if that corresponding submit button was used to submit the form here's an example of how the html could look: https://svelte.dev/repl/099c65345fe043a79774c480d3b44070?version=3.34.0
The onSubmit function could then handle different submit functions based on which property was used to submit the from.
How do you feel about something like this? if it's something you think would be interesting i'd be happy to give a PR a shot.
While the current extender API works, it feels quite hacky. A major thing that should be available is to modify or extend the Felte's configuration which currently relies on modifying the configuration object taking advantage of its mutability.
index.mjs?v=35e80513:1779 Uncaught (in promise) TypeError: Cannot read property '$$' of undefined
at init (index.mjs?v=35e80513:1779)
at new Form_1 (form.svelte? [sm]:5)
at createProxiedComponent (svelte-hooks.js:245)
at new ProxyComponent (proxy.js:239)
at new Proxy<Form> (proxy.js:339)
at create_if_block_2 (root.svelte? [sm]:38)
at Array.create_default_slot (root.svelte? [sm]:37)
at create_slot (index.mjs?v=09c160b6:69)
at create_fragment (__layout.svelte? [sm]:48)
at init (index.mjs?v=09c160b6:1799)
I've removed almost all of my code but it didn't fix the problem. So I'm getting this error with this code:
When I try to use Zod as form validator, I get errors. During in my research, I was concluded that possibly issue in wrong version of zod dependency after rollup.
Or object is null/undefined, and in condition a moment before need to check that "error" has own property "issues" otherwise should throw an error.
Hello, I'm really happy with the package and it works like intended – Thank You 👍. There is only problem that I have.
I use typescript and I get an TS-Error that I can't figure out how to solve.
I tried different variations like extend: reporter() but with no luck. I hopefully attached everything necessary to fix this. Thank you for your help :)
No overload matches this call.
Overload 1 of 2, '(config: FormConfigWithInitialValues<{ email: string; }> & { initialValues: { email: string; }; onSubmit: ({ email }: { email: string; }) => Promise<void>; validate: ValidationFunction<...>; extend: Extender<...>[]; onError: (errors: unknown) => void; }): Form<...>', gave the following error.
Type 'Extender<{ email: string; }>[]' is not assignable to type '(Extender<{ email: string; }> | Extender<{ email: string; }>[]) & Extender<{ email: string; }>[]'.
Type 'Extender<{ email: string; }>[]' is not assignable to type 'Extender<{ email: string; }>[] & Extender<{ email: string; }>[]'.
Type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/common/dist/types-1616e685").Extender<{ email: string; }>[]' is not assignable to type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/core/node_modules/@felte/common/dist/types-50c3e825").Extender<{ email: string; }>[]'.
Type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/common/dist/types-1616e685").Extender<{ email: string; }>' is not assignable to type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/core/node_modules/@felte/common/dist/types-50c3e825").Extender<{ email: string; }>'.
Types of parameters 'currentForm' and 'currentForm' are incompatible.
Property 'addValidator' is missing in type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/core/node_modules/@felte/common/dist/types-50c3e825").CurrentForm<{ email: string; }>' but required in type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/common/dist/types-1616e685").CurrentForm<{ email: string; }>'.
Overload 2 of 2, '(config: FormConfigWithoutInitialValues<{ email: string; }> & { initialValues: { email: string; }; onSubmit: ({ email }: { email: string; }) => Promise<void>; validate: ValidationFunction<...>; extend: Extender<...>[]; onError: (errors: unknown) => void; }): Form<...>', gave the following error.
Type 'Extender<{ email: string; }>[]' is not assignable to type '(Extender<{ email: string; }> | Extender<{ email: string; }>[]) & Extender<{ email: string; }>[]'.
Type 'Extender<{ email: string; }>[]' is not assignable to type 'Extender<{ email: string; }>[] & Extender<{ email: string; }>[]'.
Type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/common/dist/types-1616e685").Extender<{ email: string; }>[]' is not assignable to type 'import("/Users/jonasschell/Programming/neesh_repos/laptops_in_space_repos/monorepo/node_modules/@felte/core/node_modules/@felte/common/dist/types-50c3e825").Extender<{ email: string; }>[]'.ts(2769)
(property) FormConfigWithoutInitialValues<{ email: string; }>.extend?: Extender<{
email: string;
}> | Extender<{
email: string;
}>[]
Optional function/s to extend Felte's functionality.
Hello, first of all, what an amazingly useful library this is! It has made my life SO much better with Svelte when creating forms. I did want to point out a potential bug I have encountered and it goes like this:
If I happen to directly click on the checkbox, then the form data is updated as expected, however, if I click the button instead, the form data is not updated.
He's using a dynamic component to load the steps. If you complete step 1(email), step 2 has the error message displayed by default(password requirements...) since submit has already been used.
2) If you use a select, it displays errors by default(before submit)
I tried this with Tippy reporter as well, but it doesn't want to play nice with the svelte REPL, so I stuck with the dom reporter.
Also tried swapping out for the yup validator, but saw the same issue.
Is this the "best practices" approach towards a multistep form?
I'm not sure if this needs to be worked on at the validator or reporter level, or if it's just a feature that needs more development.
Any guidance on how to use the felte validators/reporters on a multi-step form would be greatly appreciated.
Thanks!
Ryan
p.s. Here's another approach with custom prev/next buttons and functional pre-population: https://github.com/MirrorBytes/MultiStep -- @MirrorBytes actually suggested I take a look at Felte instead since you had put in a lot of refinement and gained traction.
Here's hoping for a good way to pull it all together and have a great forms experience in Svelte.
I did a cleanup on my node_modules and did a fresh yarn install on the latest svelte. Compiler is throwing as follows:
proxy.js:19 TypeError: t2.addValidator is not a function
at n (index.ts:42)
at index.mjs:1
at Array.map (<anonymous>)
at stores (index.mjs:1)
at w (index.mjs:1)
at instance (SignupForm.svelte:380)
at init (index.mjs:1791)
at new SignupForm (SignupForm.svelte:363)
at createComponent (svelte-hooks.js:136)
at SignupForm.targetCmp.$replace (svelte-hooks.js:183)
vite v2.5.10 dev server running at:
> Local: http://localhost:4000/
SvelteKit v1.0.0-next.174
I think it's the @felte/validator-yup somehow not working anymore. I will keep looking.
When using Tippy to display validation errors, the page shows a 500 area when loading a page containing the form: 500 Cannot destructure property 'controls' of 'i' as it is undefined..
If I pass {single:true} to the reporter method, this becomes 500 t is not a function.
<script>
import {createForm} from 'felte';
import reporter from '@felte/reporter-tippy';
import * as yup from 'yup';
const schema = yup.object().shape({email: yup.string().email().required(),message: yup.string().required()});
const {form} = createForm({extend: reporter(),onSubmit: (values)=>{console.log(values);},validate: (values)=>{try{schema.validate(values);}catch(err){returnerr;}}});
</script><formuse:form><labelfor="email">Email:</label><inputid="email"name="email"aria-describedby="email-validation"/><divid="email-validation"aria-live="polite"/><labelfor="message">Message</label>
<inputtype="text"name="message"aria-describedby="message-validation"/><divid="message-validation"aria-live="polite"/><buttontype="submit">submit</button></form>
I have then tried out the traditional form.submit() (and reference the <form> element with bind:this) in Javascript, but seems like Felte is not catching the submit there.
I was attempting to setup a CodeSandBox to demonstrate another problem I was having.
While setting up the sandbox I am getting an error that I cannot get past....
"Function called outside component initialization"
CodeSandBox Error...
I do actually get this error locally but somehow SvelteKit is able to get past it and the form still works but CodeSandBox cannot. I get an error for each input on the page.
Here is my CodeSandBox. I am sure it something that I am misconfiguring due to my mediocre Svelte skills. 🤔
For a form we have to provide two different submit actions that perform different tasks and also differ in validation. To achieve that, we use the createSubmitHandler helper.
You will see a form with two simple text inputs and two submit buttons. The standard submit validates only the first input field. The second submit uses the same validation but additionally should check the validity of the second input field.
Expected Behavior
if first text input has no value:
do not perform submit for standard submit or second submit
give visual error feedback in UI for first text input
if first input has a value and second input has no value:
perform submit for standard submit but not for second submit
give visual error feedback in UI for second text input when using second submit
Actual Behavior
if first text input has no value:
does not perform submit for standard submit or second submit
gives visual error feedback in UI for first text input
if first input has a value and second input has no value:
performs submit for standard submit but not for second submit
no visual error feedback in UI for second text input when using second submit
Possible Solution
Instead of just returning the appropriate errors object in the second validation handler, additionally i can use setError to achieve our goals.
After submitting a form and receiving the response, I call reset(). It clears the input values as expected, but it also triggers all inputs to show their validation errors & shouldn't.
(I'm using DOM reporter, yup, & have required values for all inputs in my form as part of the yup validation.)
I noticed one issue, the initial Values is not working as expected.
Praise
Thanks for making such a wonderful library. (I don't have to combine svelte-forms-lib with svelte-tippyjs and svelte-scroll, just to achieve what you have done!).
Felte solves all the problems I wanted a form library to solve. For example svelte-forms-lib:
Doesn't auto focus on error field.
Has old school way of error message support, unlike using Tippy.js.
Too verbose, as I have to include onChange on every field and values as well.
Sorry. I don't know what changed, maybe Svelte Kit did. But even importing yup (without using it or using a reporter) causes the build to fail now.
Error:
TypeError: Line must be greater than or equal to 1, got -1
at BasicSourceMapConsumer.SourceMapConsumer_findMapping [as _findMapping] (/Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:67770:13)
at BasicSourceMapConsumer.SourceMapConsumer_originalPositionFor [as originalPositionFor] (/Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:67839:22)
at /Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:68799:34
at String.replace (<anonymous>)
at /Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:68789:21
at Array.map (<anonymous>)
at ssrRewriteStacktrace (/Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:68788:10)
at Object.ssrFixStacktrace (/Users/me/my-app/node_modules/vite/dist/node/chunks/dep-efe32886.js:69043:27)
at Object.get_stack (file:///Users/me/my-app/node_modules/@sveltejs/kit/dist/chunks/index.js:3359:28)
at get_response (file:///Users/me/my-app/node_modules/@sveltejs/kit/dist/ssr.js:2272:32)
Repro. Comment & un-comment the yup import to see it work or break:
and these are attached to three inputs. I think due to the way the change events are being monitored, this is what happens for instance, when I change the plain select fruit.
Wouldn't the expected behavior be that only the fruit element gets marked? Like this one:
and I did check, in none of these cases, the form is not submitting. But the $errors get updated like this in one shot:
Every step is required. But I can't use like that. If all of them are required for submitting, I can't use the button as a submit button for controlling my form. And I do have some clear options:
Handle only the last button as submittable, control my "steps" out of onSubmit (a bit boilerplatey)
Separate into 3 forms, one with each validation (less svelte composable components power)
There is an option I used on react, using the library react-hook-form
dynamically setup my validationSchema when "step" changed
It appears the dom reporter is looking for data-felte-validation-message.
The issue I'm having with this now it took two days of my life component is that, even I can inject the attribute data-felte-validation-message into it, it becomes out of sync, so when the dom-reporter is doing the querySelect on the form, it can't find it at that time.
I tested the theory with importing tick().then(() => { ...focus }) into the dom-reporter, it was partially sucessful. setTimeout was the most successful.
I'm just asking so I learn, what do you think about having the dom-reporter accept an onSubmitError callback in the options and if it is not provided then execute the standard logic?
As pictured in the default example of sveltekit, we have the perfect preconditions to build progressive forms with svelte which will work even without JS and progressively enhance with that. I quite like this feature and would like to use it with felte.
Problem is, that, if I add method="post" and action="..." to my felte-enhanced form, it will post the form with js loaded. It seems that felte doesn't do the .preventDefault().
Ideally I would like to see that usecase supported first class in felte, but if that's out of/not your scope, I'm a bit unsure what the best way would be to achive it? As far as I could see, I could overwrite the onSubmit-handler but that seem to be a lot of boilerplate for the simple intercept...
Hi, I have a use case where the initial data has an array of fields for data. You could think of it like items on a shopping list. However, when I attempt to implement it the field in the errors store becomes null instead of an array of nulls and in touched its set to true instead of an array of falses.
I've tried to convert the array to an object with integer indexes as keys, which works. The errors and touched show the correct object structure but they do not seem to update (errors does not get the error messages returned from the validation function and touched does not change the fields to true when changed). It also causes some issues with validating.
letvalidateSchema=yup.object().shape({title: yup.string().trim().required("Title Required!"),url: yup.string().url("URL must be Proper!").required("Job URL is Required!"),salary: yup.object({min: yup.number("Salary must be a number!").positive("Salary must be greater than 0!").integer("Salary must be an Integer!"),max: yup.number("Salary must be a number!").positive("Salary must be greater than 0!").integer("Salary must be an Integer!").moreThan(yup.ref("min"),"Max Salary must be larger than Minimum!"),currency: yup.string().required(),}),});
However the message shown is not the one I specified:
Hi, I don't mean to take too much of your time, but wanted to let you know about another edge case I ran into and what I did to bypass it somewhat.
There is this nice custom component called svelte-select.
Normally, this component doesn't use a named input field, as the input field is only used to filter the select options, and it gives you an on:select handler. So, if you use this with use:form, you get this:
index.ts:149 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'focus')
at Object.onSubmitError (index.ts:149)
at index.mjs:1
at Array.forEach (<anonymous>)
at HTMLButtonElement.<anonymous> (index.mjs:1)
I fixed it with a fork of the package and using a hidden named input, and tested various scenarios including transforms. It wasn't pleasant but it was still easier than writing a component from scratch. Just wanted to let you know of the error encountered.
Hello, would it be consistent with the purpose of felte to add a few extra event listeners like some sort of hooks. I have two different use cases.
Captcha preprocessing
Invoking animations on 3rd party UI libraries.
captcha preprocessing:
At the moment, I have attached the recaptcha execution to the onSubmit handler, I also understand I could have done this the other way around, call the recaptcha first and then captchaOnSuccess, call createForm submitButtonHandler. But I figured it would be better to initiate captcha the last in case the token expires or something.
onSubmit: async (values) => {
debug("launching recaptcha");
await recaptcha.execute();
} /*
│at this point all validations have passed and it
│is okay to launch captcha sequence. rest of the
│data submission will proceed after captcha
│succeeds and emits onSuccess event.
*/,
createform -> validations -> onSubmit -> execute captha function -> onCaptchaSuccess -> doPost the form inside the callback ----> do server side validation return json -> use setError to update the form.
This is working fine, but it's a bit of flow jumping. Especially with the second round the form is submitted if there are server side validation errors. You see, for instance if I setError(phoneNumber) because it is invalid on the server side (although it looks valid syntax wise), it disrupts my use of validate and $isSubmitting and $isValid in my validate() function, which I use to display some toasts in an effort to assist the user with a visual cue.
validate: (values) => {
if ($isSubmitting && !$isValid) {
debug("launching custom validation");
showToast({
title: "Incomplete Request",
description: "Please check your inputs",
type: "warning",
showProgress: false,
});
}
} /*
│this is custom validation logic, we basically
│use it to show some toast notification as a form
│of user feedback. All validations actually
│happen through yup validator.
*/,
when setError() is done on the phoneNumber , it would still launch the toast, and proceed with the server post. I tried setTouch but not sure how that works. I couldn't find a way to reset the errors, the reset function appear to be resetting everything wiping out the user entered form data.
So, I think it could be helpful, if there were some beforeSubmit, afterSubmit, beforeValidate, aterValidate hooks, and some way to clear error states so it could be possible to trigger some css animations. Otherwise, user might feel like nothing is happening when they press the submit button (that's the reason why I put the toast there like that).
P.S the closest resetError I could do was:
for (const [k, v] of Object.entries(values)) {
setError(k, false);
}
inside the validate(). But because the validate executes several times during init, and every time the during focus change, it animates the whole form. I was able to work around this by using $isSubmitting however.
With the DOM reporter, I can submit the form data successfully. But I can't get it to show an error message if the validation failed--it shows no error in the DOM nodes for the validation error messages, but it logs to the browser console: Uncaught (in promise) ValidationError: email must be a valid email
<script>
import {createForm} from 'felte';
import reporterDom from '@felte/reporter-dom';
import reporter from '@felte/reporter-tippy';
import * as yup from 'yup';
const schema = yup.object().shape({email: yup.string().email().required(),message: yup.string().required()});
const {form} = createForm({extend: reporterDom(),onSubmit: (values)=>{console.log('values',values);},validate: (values)=>{try{schema.validate(values);}catch(err){returnerr;}}});
</script><form use:form><labelfor="email">Email</label><inputid="email"type="text"name="email"aria-describedby="email-validation"/><divid="email-validation"data-felte-reporter-dom-for="email"aria-live="polite"/><labelfor="message">Message</label><inputid="message"type="text"name="message"aria-describedby="message-validation"/><divid="message-validation"data-felte-reporter-dom-for="message"aria-live="polite"/><buttontype="submit">submit</button></form>
Just noticed that there is a bug with @felte/common when building a sveltekit-app using @sveltejs/adapter-node@next. The problem disappears if I remove the adapter. The problem doesn't exist when doing a named import from f.e. the felte-package itself...
I'm unsure why this happens as the correct esm-package seems to be built and is referenced in @felte/common's package.json... There seem to be a subtle difference in the rollup-config which makes the adapter unhappy ;)
vite v2.4.4 building SSR bundle for production...
✓ 74 modules transformed.
.svelte-kit/output/server/app.js 199.25kb
Run npm run preview to preview your production build locally.
> Using @sveltejs/adapter-node
> Named export 'getPath' not found. The requested module '@felte/common' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@felte/common';
const { getPath } = pkg;
file:///[..]/.svelte-kit/output/server/app.js:2
import { getPath } from "@felte/common";
^^^^^^^
SyntaxError: Named export 'getPath' not found. The requested module '@felte/common' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@felte/common';
const { getPath } = pkg;
Reproduction
npm init svelte@next felte-esm-repro && cd felte-esm-repro && npm i && npm add -D @felte/common @sveltejs/adapter-node@next
Use the default for every question by hitting enter.
Add the following to your svelte.config.js:
importnodefrom'@sveltejs/adapter-node';/** @type {import('@sveltejs/kit').Config} */constconfig={kit: {adapter: node(),// hydrate the <div id="svelte"> element in src/app.htmltarget: '#svelte'}};exportdefaultconfig;
Add the following to src/routes/index.svelte:
<script>
import {getPath} from "@felte/common";
import Counter from '$lib/Counter.svelte';
console.log(getPath);
</script>