flowbite-svelte icon indicating copy to clipboard operation
flowbite-svelte copied to clipboard

Modal not displaying when wrapped in {#if} in a separate component

Open romrom21 opened this issue 9 months ago • 5 comments

Describe the bug

When a Modal is used inside a separate component and that component is conditionally rendered using {#if}, the modal does not appear when open is set to true.

This behavior worked in old versions, but appears to be broken in the current version.

Reproduction

<!-- ParentComponent.svelte -->
<script>
  import ExternalModal from './ExternalModal.svelte';
  let isModalOpen = false;
</script>

<button onclick={() => isModalOpen = true}>Open Modal</button>

{#if isModalOpen}
  <ExternalModal bind:isOpen={isModalOpen} />
{/if}
<!-- ExternalModal.svelte -->
<script>
  import { Modal } from 'flowbite-svelte';
  import { onMount } from 'svelte';

  interface Props {
    isOpen: boolean
  }

  let { isOpen = $bindable()}: Props = $props()

  onMount(() => {
    console.log('Modal mounted');
  });
</script>

<Modal bind:open={isOpen} />

Version and System Info

System:
    OS: Windows 11 10.0.26100
    CPU: (22) x64 Intel(R) Core(TM) Ultra 7 155H
    Memory: 9.17 GB / 31.46 GB
  Binaries:
    Node: 22.14.0 - C:\Program Files\nodejs\node.EXE
    npm: 11.3.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 137.0.7151.55

romrom21 avatar Jun 03 '25 09:06 romrom21

Try this:

// MyModal.svelte
<script lang="ts">
  import { Modal } from 'flowbite-svelte';
  import { onMount } from 'svelte';

  interface Props {
    isOpen: boolean
  }

  let { isOpen = $bindable()}: Props = $props()

  onMount(() => {
    console.log('Modal mounted');
  });
</script>

<Modal title="Terms of Service" bind:open={isOpen} autoclose>
  <p class="text-base leading-relaxed text-gray-500 dark:text-gray-400">With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, companies around the world are updating their terms of service agreements to comply.</p>
  <p class="text-base leading-relaxed text-gray-500 dark:text-gray-400">The European Union’s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant to ensure a common set of data rights in the European Union. It requires organizations to notify users as soon as possible of high-risk data breaches that could personally affect them.</p>
</Modal>
// +page.svelte
<script>
  import { Button } from "flowbite-svelte"
  import MyModal from './MyModal.svelte';
  let isModalOpen = false;
</script>

<Button onclick={() => isModalOpen = true}>Open Modal</Button>

<MyModal bind:isOpen={isModalOpen} />

shinokada avatar Jun 03 '25 11:06 shinokada

Yes, but if I have a required item in the props and I want to do something based on this item in onMount, I can't since the modal is already mounted but hidden.

romrom21 avatar Jun 03 '25 12:06 romrom21

I'm not quite sure if it is a good idea to use Modal for your purpose.

shinokada avatar Jun 08 '25 11:06 shinokada

What should I use then? Because I think modals are the best way to implement this 😕

romrom21 avatar Jun 09 '25 07:06 romrom21

@shinokada

romrom21 avatar Jun 16 '25 09:06 romrom21

I have the same issue. any fix for it?

jonm01 avatar Jun 18 '25 16:06 jonm01

Unfortunately, the fix doesn't seem to be working @jonm01 can you confirm on your side ?

romrom21 avatar Jun 26 '25 08:06 romrom21

Just tested again and works correctly.

@shinokada have you published the latest to the npmjs.org ?

jjagielka avatar Jun 26 '25 09:06 jjagielka

I just published v1.8.2.

shinokada avatar Jun 26 '25 12:06 shinokada

@jjagielka I have the same problem than @romrom21 in 1.8.2 For exemple this doesn't work and the modal doesn't display:

Image

Stunkell avatar Jul 03 '25 08:07 Stunkell

@romrom21 wrote:

if I have a required item in the props and I want to do something based on this item in onMount, I can't since the modal is already mounted but hidden.

Can you show me a code example of what you're talking here about? I must admit I can't really understand the problem.

jjagielka avatar Jul 06 '25 13:07 jjagielka

@jjagielka I think that I have the same constraint than @romrom21.

I have many Tables where I want to be able to click on an item to display a Modal or Drawer to show all this item's details. By default the item is undefined or null as I never clicked on any. So I should not instantiate the Modal until one is clicked.

Here is a simplified exemple of the problem:

A component that use the modal:

<script lang="ts">
    import { Button } from "flowbite-svelte"
    import BasicModal from "./BasicModal.svelte"

    let open = $state(false)

    let items = [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }]

    let selectedItem = $state(undefined)

    function selectItemAndDisplayModal(item: any) {
        selectedItem = item
        open = true
    }
</script>

{#each items as item}
    <Button onclick={() => selectItemAndDisplayModal(item)}>{item.name}</Button>
{/each}

{#if open}
    <BasicModal bind:open itemToDisplay={selectedItem} />
{/if}

A modal that needs to display an item

<script lang="ts">
   import { Modal } from "flowbite-svelte"

   interface Props {
       open: boolean
       itemToDisplay: any
   }

   let { open = $bindable(), itemToDisplay }: Props = $props()
</script>

<Modal bind:open>
   <p>{itemToDisplay.name}</p>
</Modal>

Stunkell avatar Jul 09 '25 10:07 Stunkell

@Stunkell - why do you need that if wrapper around BasicModal. If I skip it your example works perfectly good.

{#each items as item}
    <Button onclick={() => selectItemAndDisplayModal(item)}>{item.name}</Button>
{/each}

<BasicModal bind:open itemToDisplay={selectedItem} />

jjagielka avatar Jul 09 '25 10:07 jjagielka

@jjagielka Here it works because my example is simplified and the itemToDisplay is of type any.

Also the problem would be when you have a modal that is a form with some inputs. If you don't wrap it into if, the previous values are still set when you open again the modal.

Stunkell avatar Jul 09 '25 11:07 Stunkell

previous values are still set when you open again the modal.

No, they are not. Dialog gets destroyed once it receives open={false} and is re-created when open={true}. So even if you have a form inside the values will disappear - and that's a limitation of the current implementation.

jjagielka avatar Jul 09 '25 11:07 jjagielka

@Stunkell I found a workaround, wrap your if in a {#key } it works

romrom21 avatar Jul 09 '25 11:07 romrom21

@romrom21 - Why? Can you please explain why do you need to wrap the modal in that additional if/key?

jjagielka avatar Jul 09 '25 11:07 jjagielka

previous values are still set when you open again the modal.

No, they are not. Dialog gets destroyed once it receives open={false} and is re-created when open={true}. So even if you have a form inside the values will disappear - and that's a limitation of the current implementation.

In my case it does not work the values of the form are still in there

romrom21 avatar Jul 09 '25 11:07 romrom21

@jjagielka If I need actions to be performed in the onMount of the modal I can't since it is called only once. If I wrap it in an if, each time I open the modal, the onMount is called

romrom21 avatar Jul 09 '25 11:07 romrom21

@jjagielka Are you sure ? I just tried with a modal in my project and when I close it and re open it, I still have the same value: First open: Image

I fill the modal: Image

Closing my modal: Image

Re openning it:

Image

Stunkell avatar Jul 09 '25 11:07 Stunkell

@romrom21 - why can't you use {@attachment ...}. It's designed for that purpose.

jjagielka avatar Jul 09 '25 11:07 jjagielka

@Stunkell - in your screens shots seems like a from is not in the dialog, but you are using the dialog to confirm another form sending. And that's a different case.

jjagielka avatar Jul 09 '25 11:07 jjagielka

Oh it is, I just cropped the screenshot I didn't wanted to blur some parts:

Image

Stunkell avatar Jul 09 '25 11:07 Stunkell

You have:

  • dialog 1: containing the form with data
  • dialog 2: opened when you save to confirm the choice

If you open/close dialog2, sure nothing will happen with the form in dialog1, unless you catch dialog2.onclose and make form clean up.

However if you open/close dialog1, the form data will be cleared out.

jjagielka avatar Jul 09 '25 11:07 jjagielka

Really sorry I'm not sure to understand. The dialog 1 is closed by the confirmation of the dialog2:

Image

Stunkell avatar Jul 09 '25 12:07 Stunkell

Can you please show the code? Simplified one. Or point me out to your project if it's on github. Or create demo elsewhere.

jjagielka avatar Jul 09 '25 12:07 jjagielka

Ok here is a simplified version that is not working (keeping the value even after closing and opening again) on my side:

The modal:

<script lang="ts">
    import { Input, Modal } from "flowbite-svelte"

    interface Props {
        open: boolean
    }

    let { open = $bindable() }: Props = $props()

    let test = $state("")
</script>

<Modal bind:open>
    <form>
        <Input bind:value={test} type="text" placeholder="Type here..." />
    </form>
</Modal>

The component using the modal:

<script lang="ts">
    import { Button } from "flowbite-svelte"
    import BasicModal from "./BasicModal.svelte"

    let open: boolean = $state(false)
</script>

<Button onclick={() => (open = true)}>Open</Button>

<BasicModal bind:open />

Stunkell avatar Jul 09 '25 12:07 Stunkell

Ah, I get it now. It's not form that keeps the values, but your local variable test that refreshes the form on new show.

So, then indeed you need to wrap that BasicModal in {#key open} or {#if open} directive. Anyway, it works for me if I do that.

<Button onclick={() => (open = true)}>Open</Button>
{#if open}
<BasicModal bind:open />
{/if}

Doesn't it work for you?

jjagielka avatar Jul 09 '25 13:07 jjagielka

It seems like after a npm i command the {#if} works! I don't know what I did wrong last time but I'm on 1.8.2 since 1 week know and just doing a new npm i solved the problem.

Really sorry about all this sub and the time taken. Thank you a lot for the time you dedicated to our problem. Have a nice day

Stunkell avatar Jul 09 '25 13:07 Stunkell

Just for completeness, this is another approach to your problem. No need for if/key wrappers, no need for many open variables.

<script>
  import { Button, Input, Modal } from "flowbite-svelte";
  import { mount, unmount } from "svelte";

  // this is re-usable can be separate file
  async function runModal(children, props) {
    return new Promise((resolve, reject) => {
      let result = {};
      const onaction = (params) => (result = params);
      const onclose = () => {
        unmount(component);
        resolve(result);
      };
      const component = mount(Modal, { target: document.body, props: { children, onaction, onclose, open: true, ...props } });
    });
  }

  async function getValue(title) {
    let { action, data } = await runModal(basicModal, { title, form: true, class: "max-w-xs" });
    console.log(action, data);
  }
</script>

<Button onclick={() => getValue("Get value")}>Func</Button>

{#snippet basicModal()}
  <Input name="your_variable" />
  <div class="flex justify-center gap-4">
    <Button type="submit" value="yes">Yes</Button>
    <Button type="submit" value="no">No</Button>
  </div>
{/snippet}

jjagielka avatar Jul 10 '25 20:07 jjagielka