Modal not displaying when wrapped in {#if} in a separate component
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
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} />
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.
I'm not quite sure if it is a good idea to use Modal for your purpose.
What should I use then? Because I think modals are the best way to implement this 😕
@shinokada
I have the same issue. any fix for it?
Unfortunately, the fix doesn't seem to be working @jonm01 can you confirm on your side ?
Just tested again and works correctly.
@shinokada have you published the latest to the npmjs.org ?
I just published v1.8.2.
@jjagielka I have the same problem than @romrom21 in 1.8.2 For exemple this doesn't work and the modal doesn't display:
@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 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 - 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 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.
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.
@Stunkell I found a workaround, wrap your if in a {#key } it works
@romrom21 - Why? Can you please explain why do you need to wrap the modal in that additional if/key?
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 whenopen={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
@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
@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:
I fill the modal:
Closing my modal:
Re openning it:
@romrom21 - why can't you use {@attachment ...}. It's designed for that purpose.
@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.
Oh it is, I just cropped the screenshot I didn't wanted to blur some parts:
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.
Really sorry I'm not sure to understand. The dialog 1 is closed by the confirmation of the dialog2:
Can you please show the code? Simplified one. Or point me out to your project if it's on github. Or create demo elsewhere.
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 />
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?
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
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}