0

I've got the following route:

- routes
   - artists
      - [slug]
      - new
         - +page.server.js
         - +page.svelte

where new/+page.svelte is the following:

<script>
    import ArtistForm from "../../../components/forms/ArtistForm.svelte";

    /* data returned from actions function in +page.server.js */
    export let form;
</script>

<ArtistForm formData={form} mode="new" />

and +page.svelte simply has the action that has to be performed when the form is submitted:

export const actions = {
    default: async ({ request, fetch }) => {

        const artistAPI = new ArtistAPI(fetch);
        const data = Object.fromEntries(await request.formData());
        const artist = artistDTO(data);

        // if errors returned from building the DTO (validation errors) then return the object.
        if (artist?.errors && !emptyObject(artist.errors)) {
            return {
                "data": artist.data,
                "errors": artist.errors,
            };
        }

        const response = await artistAPI.post(artist);
        if (response.status == 200) {
            throw redirect(302, '/artists')
        }

        return {
            "data": artist.data,
            "errors": response.errors,
        }
    }
};

Now, this works fine on its own. Whenever I access artists/new I can create new artists.

The issue is the following:

I've got a routes/records/new route. When creating a record I want to allow an user to dynamically create an artist via a modal if the artist doesn't exists. To do so I'm loading a modal and within the modal I'm loading the <ArtistForm> component. The component has a <form> element and a button to submit the form.

This is the piece of code within routes/records/new where the component is loaded:

{:else if activeStep == "CreateArtist"}
        <div>Artist does not exist, please add it.</div>
        <ArtistForm loadedAsModal={ true } />
        <div class="mt-7 flex justify-center">
            <button class="btn btn-outline btn-primary" type="submit" on:click={handleSubmit}>Create Artist</button>
        </div>
{/if}

The problem is when I click on the save button. When the form within the modal is submitted is calling the action defined in routes/records/new/+page.server.js. The artist's action is not triggered which I think is correct since I'm loading the component in routes/records/new but the action lives in routes/artists/new/+page.server.js.

I was thinking to perhaps add a callback method passed as prop to <ArtistForm> but then I'd have to duplicate the logic so the callback can get the data from the form and perform the POST request.

Is there any way around this without (or with the minimum) code duplication?

5
  • 2
    Setting the <form> action attribute to /artists/new/ solves the problem?
    – Peppe L-G
    Commented Jul 6 at 8:02
  • yes! that worked, thanks. Add it as a response so I can mark it as the correct answer. One last question: after submitting, there's an obj that is returned from the action, this object is supposed to be assigned to the form object in ArtistForm, but when I print it, it is undefined, yet if I print it in the action it has some value. The <form> element in <ArtistForm> has on:submit|preventDefault="{handleSubmit}" so I'm expecting to be able to print the value of form object in handleSubmit which is not happening. If I remove the on:submit then I see it when doing $: console.log(form)
    – MrCujo
    Commented Jul 6 at 19:37
  • Thanks, but feel free to answer your own question. If you have on:submit|preventDefault="{handleSubmit}", then you can't access form in handleSubmit. handleSubmit will be called before the request is sent to the server, and form will be populated with data after the web browser has received the response for that request. So you have to use reactivity ($: syntax) to listen for when form has obtained a new value if you want to "listen" to the response.
    – Peppe L-G
    Commented Jul 7 at 7:38
  • Thanks @PeppeL-G . I figured it out. I'm using use:enhance and calling a callback method with the data returned in results.
    – MrCujo
    Commented Jul 7 at 14:20
  • Someone should post this as an answer (which then should be accepted).
    – brunnerh
    Commented Jul 7 at 19:24

1 Answer 1

0

As mentioned by @PeppeL-G we can make it work by setting the action attribute to /artists/new/. This way, when the form is submitted it'll hit the action defined in /artists/new/+page.server.js from wherever the form is loaded, in my case, from a modal that is loaded in a different route.

Now, the other issue I was facing was the following: when the form was loaded in a modal and after it was successfully submitted, I needed the data returned in the route where the modal was loaded. To achieve this, I added a callback property to ArtistForm. This callback function will be passed from the route where the component is loaded, I call it processData in this case:

{:else if activeStep == "CreateArtist"}
        <div>Artist does not exist, please add it.</div>
        <ArtistForm loadedAsModal={ true } callback={processData}/>
{/if}

In ArtistForm I'm using use:enhance which receives a function with a result param, this param holds the data value of the form, meaning the data returned from the action when the form is submitted. Having this data on hand I can simply pass it over to the callback function and do whatever I want in the route where the component was instantiated:

<form method="POST" class="pt-10" action="/artists/new" use:enhance={() => {
        return async ({ result }) => {

            if (!loadedAsModal && (result.status >= 200 && result.status < 300) && !('errors' in result.data)) {
                await goto("/artists");
                return;
            }

            if (('errors' in result.data)) {
                return;
            }
            callbackFn(result);

        }
    }}>

...
</form>

Not the answer you're looking for? Browse other questions tagged or ask your own question.