ciscoheat / sveltekit-superforms

Making SvelteKit forms a pleasure to use!
https://superforms.rocks
MIT License
2.25k stars 65 forks source link

See if any forms are submitting #213

Open liegeandlief opened 1 year ago

liegeandlief commented 1 year ago

Is your feature request related to a problem? Please describe.

As discussed in https://github.com/ciscoheat/sveltekit-superforms/issues/201 it would be helpful to know if any forms are currently submitting. For example it could be used to show a global loading indicator without having to add logic to every form instance.

Describe the solution you'd like

Something like:

<script>
import { anyFormsSubmitting } from 'sveltekit-superforms/client'
import GlobalLoadingIndicator from '$lib/components/GlobalLoadingIndicator.svelte'
</script>

<!-- where $anyFormsSubmitting is a boolean -->
{#if $anyFormsSubmitting}
    <GlobalLoadingIndicator />
{/if}

Describe alternatives you've considered

This could be managed by creating a derived store with the $submitting value for each form but this would create duplication and it would be easy to forget to include a particular form.

ciscoheat commented 1 year ago

This could possibly be combined with issuing a warning if there are multiple forms with the same id present on the same page.

ciscoheat commented 1 year ago

After testing it out, it's both a rather complicated and expensive operation (a dynamically derived store cannot be directly accessed), so I have to say no to this feature, sorry.

liegeandlief commented 1 year ago

Where does the complexity come from? Could it not be something as simple as this:

import { writable, derived } from 'svelte/store'

const submittingForms = writable<string[]>([])
export const areAnyFormsSubmitting = derived(submittingForms, ($submittingForms) => $submittingForms.length > 0)

export const addSubmittingForm = (id: string) => {
    submittingForms.update((ids) => [...ids, id])
}

export const removeSubmittingForm = (id: string) => {
    submittingForms.update((ids) => ids.filter((i) => i !== id))
}

addSubmittingForm would be called in the same place where submitting for an individual form is set to true. removeSubmittingForm would be called in the same place where submitting for an individual form is set to false and could also be manually called in the onDestroy of a component where the form is used.

ciscoheat commented 1 year ago

Identical id:s can exist on the same page, new forms can be added/returned in different places and needs to be tracked, the submitting stores needs to be checked with get in the derived store, making it expensive, for example.

liegeandlief commented 1 year ago

I didn't quite understand your previous comment so I created a proof-of-concept to illustrate my suggestion - https://github.com/ciscoheat/sveltekit-superforms/pull/226

ciscoheat commented 1 year ago

The thing is that there is already a $submitting store for the forms, using that would be the optimal solution, but it's quite complicated for said reasons. Your POC is kind of a double-work in that sense, it works but is not really related to superforms, it's basically its own action, so maybe it's better to use it that way. It's very hard to draw the line somewhere, but I'm reopening this anyway.

saturnonearth commented 1 year ago

I did this in my own way by using a derived store and just saved "submitting" to it.

$: $projectStore.name.submitting = $submitting;
export const saving = derived(
    [itemStore, projectStore],
    ([$itemStore, $projectStore]) => $itemStore.submitting || $projectStore.name.submitting
);
liegeandlief commented 1 year ago

Not sure I completely understand your solution @saturnonearth but it looks like it requires adding the submitting state of each form to the derived store, which is what I'm trying to avoid.

I'd like the library to inform me if any form is submitting, in much the same way as SvelteKit informs me if a navigation is occurring via the navigating store provided by $app/stores.

fehnomenal commented 1 year ago

Hi, I also needed this and came up with the following helper function:

import { navigating } from '$app/stores';
import { derived, writable } from 'svelte/store';
import { superForm } from 'sveltekit-superforms/client';

const submittingCount = writable(0);

export const loading = derived(
  [navigating, submittingCount],
  ([$navigating, $submittingCount]) => $navigating !== null || $submittingCount > 0,
);

export const superFormWithLoading: typeof superForm = (form, options) => {
  const _form = superForm(form, options);

  let isTracked = false;

  _form.submitting.subscribe(($submitting) => {
    if (!isTracked) {
      isTracked = true;
    } else if ($submitting) {
      submittingCount.update(($c) => $c + 1);
    } else {
      submittingCount.update(($c) => $c - 1);
    }
  });

  return _form;
};

isTracked is used for skipping the first subscriber update which is not a real change but the initial value of false. Otherwise the count would be initiated with -1.