Open alexbjorlig opened 9 months ago
cc @JReinhold
I have an idea of what's going on here.
Under the hood sveltekit-superforms
uses enhance
from $app/forms
, we can see that here: https://github.com/ciscoheat/sveltekit-superforms/blob/main/src/lib/client/superForm.ts#L1588
However we mock that out, which you can see if you add a function at parameters.sveltekit_experimental.forms.enhance
, that function will be called.
The problem is that the real enhance
function takes in a second argument submit
, which is a callback it calls when a form submits. Superform expects this callback to be called, which I guess it will then pass on to the onSubmit
function that the user defines, however our mock doesn't call that callback ever.
Our mock never calls this second submit
argument and we could try to do that, however it's not trivial to do, because SvelteKit does some heavy transformation before calling the callback, and we'd need to do the same thing, except calling fetch
which it also does. Maybe we can just copy the function body to our own mock to do the same transformations and call the submit
argument?
In a perfect world we would just call SvelteKit's enhance
but then somehow stop the fetch
call from happening, but I'm not sure how that would be possible.
A lighter weight solution would be to allow users to call that submit
argument in their mock definitions, eg.:
// Button.stories.ts
const meta = {
component: Button,
...,
parameters: {
sveltekit_experimental: {
forms: {
enhance: (formElement, submit) => {
// do any custom stuff
// eg. call submit(myMockData);
}
}
},
},
} satisfies Meta<Button>;
This would be easier to support, we just need to make sure formElement
and submit
are passed to the mock which they aren't right now. This would put the burden on the user to figure out how to "mimic" what SvelteKit does under the hood, if they wanted to replicate that (which I think they would).
Any thoughts on all of this @paoloricciuti @benmccann?
Solution A seems especially complicated because you have to keep it in sync with SvelteKit although is far better for the users since in that case they would have to maintain that themselves. I can try to do a better mocking that more closely matches the sveltekit implementation. I wonder what the best api for that should be tho, to allow who doesn't want to mock api calls to still get the event.
Hi @JReinhold - thanks for looking into this 🙏
At 21RISK we are adopting Sveltekit superforms, developed by Andreas Söderlund. The library won Svelte Hack 2023 best library - it's very popular for handling forms.
We engaged Andreas on this issue, and he created this PR - very much your solution B 🤓
It's essentially doing more or less exactly what you proposed here.
-export function enhance(form: HTMLFormElement) {
- const listener = (e: Event) => {
- e.preventDefault();
- const event = new CustomEvent('storybook:enhance');
- window.dispatchEvent(event);
- };
- form.addEventListener('submit', listener);
- return {
- destroy() {
- form.removeEventListener('submit', listener);
- },
- };
+import type { SubmitFunction } from '@sveltejs/kit';
+
+export type EnhanceData = {
+ formElement: HTMLFormElement;
+ submitFunction: SubmitFunction;
+ submitEvent: SubmitEvent;
+};
+
+export function enhance(formElement: HTMLFormElement, submitFunction: SubmitFunction) {
+ const listener = (e: Event) => {
+ const event = new CustomEvent('storybook:enhance', {
+ detail: {
+ formElement,
+ submitFunction,
+ submitEvent: e
+ }
+ });
+ window.dispatchEvent(event);
+ };
+ formElement.addEventListener('submit', listener);
+ return {
+ destroy() {
+ formElement.removeEventListener('submit', listener);
+ }
+ };
}
Maybe long term something like solution A could be implemented, but would it be possible to adopt solution B here and now 😅
Solution B sounds like the most feasible, since as you said, SvelteKit does a lot both before and after the callback. I've come quite far with this PR but it's specific to Superforms.
There could be a solution C, since the storybook:enhance
event which is triggered in the current mock enhance, could be expanded to include all necessary data in solution B. Here's my suggestion (source):
import type { SubmitFunction } from '@sveltejs/kit';
export type EnhanceData = {
formElement: HTMLFormElement;
submitFunction: SubmitFunction;
submitEvent: SubmitEvent;
};
export function enhance(formElement: HTMLFormElement, submitFunction: SubmitFunction) {
const listener = (e: Event) => {
const event = new CustomEvent('storybook:enhance', {
detail: {
formElement,
submitFunction,
submitEvent: e
}
});
window.dispatchEvent(event);
};
formElement.addEventListener('submit', listener);
return {
destroy() {
formElement.removeEventListener('submit', listener);
}
};
}
export function applyAction() {}
export function deserialize() {}
This should be more flexible, since it allows you to hook into storybook:enhance
anywhere, instead of having to do it in the metadata for the stories.
I'm okay with Solution B/C. I think your proposal would be the way to implement it, however the fact that storybook:enhance
is a global event is an implementation detail that I don't want to support officially forever. The supported way to use it would be in the mock definitions, users are free to listen for the global event and do stuff, but they should expect that to break in any patch release.
PRs welcome!
Hi @JReinhold - here you go 😅 https://github.com/storybookjs/storybook/pull/26338
I'm a beginner web developer , but it seems i have a similar behaviour in my superform. My form button does not trigger any form actions on the server side. In the same project another form has no issues with this.
I use a combination of shadcn/sveltekit/superforms, so i don't recognize stories etc. Can someone how i can implement solution B in my project?
Do i have to edit: node_modules/@sveltejs/kit/src/runtime/app/forms.js ?
I'm a beginner web developer , but it seems i have a similar behaviour in my superform. My form button does not trigger any form actions on the server side. In the same project another form has no issues with this.
I use a combination of shadcn/sveltekit/superforms, so i don't recognize stories etc. Can someone how i can implement solution B in my project?
Do i have to edit: node_modules/@sveltejs/kit/src/runtime/app/forms.js ?
What do you mean you don't recognize stories? Are you using storybook?
I'm a beginner web developer , but it seems i have a similar behaviour in my superform. My form button does not trigger any form actions on the server side. In the same project another form has no issues with this. I use a combination of shadcn/sveltekit/superforms, so i don't recognize stories etc. Can someone how i can implement solution B in my project? Do i have to edit: node_modules/@sveltejs/kit/src/runtime/app/forms.js ?
What do you mean you don't recognize stories? Are you using storybook?
I'm sorry, but i've never used it. I read a little documentation, but seems i have to take a little time for that. Is it possible to implement the bug fix without using storybook?
I'm a beginner web developer , but it seems i have a similar behaviour in my superform. My form button does not trigger any form actions on the server side. In the same project another form has no issues with this. I use a combination of shadcn/sveltekit/superforms, so i don't recognize stories etc. Can someone how i can implement solution B in my project? Do i have to edit: node_modules/@sveltejs/kit/src/runtime/app/forms.js ?
What do you mean you don't recognize stories? Are you using storybook?
I'm sorry, but i've never used it. I read a little documentation, but seems i have to take a little time for that. Is it possible to implement the bug fix without using storybook?
This is a bug IN storybook not solved by it. Sorry but you need to continue your search 😄
I'm loving both SuperForms and Storybook, but facing this same issue. Thanks for all the work in it so far!
Does anyone have advice for setting up a local workaround until one of the proposed solutions makes it into a Storybook release? I'm only using SuperForms in SPA mode (so no actual form submissions to the server), if that makes it any easier.
I'm loving both SuperForms and Storybook, but facing this same issue. Thanks for all the work in it so far!
Does anyone have advice for setting up a local workaround until one of the proposed solutions makes it into a Storybook release? I'm only using SuperForms in SPA mode (so no actual form submissions to the server), if that makes it any easier.
Until they fix the issue, you can workaround this by applying a path to storybook
. I had never tried this before, but it's not so scary. If you use npm you can use this package.
When you are done, this is essentially what you changed:
diff --git a/node_modules/@storybook/sveltekit/src/mocks/app/forms.ts b/node_modules/@storybook/sveltekit/src/mocks/app/forms.ts
index d1b2686..32333f9 100644
--- a/node_modules/@storybook/sveltekit/src/mocks/app/forms.ts
+++ b/node_modules/@storybook/sveltekit/src/mocks/app/forms.ts
@@ -1,14 +1,28 @@
-export function enhance(form: HTMLFormElement) {
+import type { SubmitFunction } from '@sveltejs/kit';
+
+export type EnhanceData = {
+ formElement: HTMLFormElement;
+ submitFunction: SubmitFunction;
+ submitEvent: SubmitEvent;
+};
+
+
+export function enhance(formElement: HTMLFormElement, submitFunction: SubmitFunction) {
const listener = (e: Event) => {
- e.preventDefault();
- const event = new CustomEvent('storybook:enhance');
+ const event = new CustomEvent('storybook:enhance', {
+ detail: {
+ formElement,
+ submitFunction,
+ submitEvent: e
+ }
+ });
window.dispatchEvent(event);
};
- form.addEventListener('submit', listener);
+ formElement.addEventListener('submit', listener);
return {
destroy() {
- form.removeEventListener('submit', listener);
- },
+ formElement.removeEventListener('submit', listener);
+ }
};
}
And with change you can have stories with superforms - and you can even mock submissions to the server with mws 😁
I'm using Bun, so it took a bit of tweaking but I got this working – thank you!
For reference, I used bun patch @storybook/sveltekit
, then edit the file.
And I've already got msw
set up for other API calls, so now I'm all good to go :-)
I'm using Bun, so it took a bit of tweaking but I got this working – thank you!
For reference, I used
bun patch @storybook/sveltekit
, then edit the file.And I've already got
msw
set up for other API calls, so now I'm all good to go :-)
That's great to hear. Tell all use still using node.js, how is bun/svelte/storybook coming along - are you happy with the setup?
That's great to hear. Tell all use still using node.js, how is bun/svelte/storybook coming along - are you happy with the setup?
I am very happy with it! The switch was mostly seamless, with only minor things here or there that needed some extra attention to get working. The most tricky thing was something in the fetch
api that wasn't fully implemented in the way svelte needed (since it provides a custom fetch
function during requests), but even there it wasn't too difficult to find a workaround.
And bun feels really fast and pleasant to use.
And bun feels really fast and pleasant to use.
Ok sounds nice, if you ever write a blog about it please drop a link ☺️ I'm mostly satisfied with the performance/speed of node.js, but you know, it's always interesting what's going on at the neighbors party 🤔
Describe the bug
When I click the submit button, nothing happens if my form is using
use:enhance
fromsveltekit-superforms
.A form without
use:enhance
seems to work great.https://github.com/storybookjs/storybook/assets/9967422/beda05ef-f2a3-402c-8cd0-c71b0e684ce6
Button.svelte
To Reproduce
Clone the repo,
npm run storybook
and visit theButton.svelte
story.System