reactjsnext.jsshadcnui

Shadcn UI disappear when changing state inside Dialog component (NextJS v14) App Router


I want to give simple loading state using useState hook inside Dialog component. However, changing the isResultReturned state from false to be true will make the dialog disappear. And I also see the setIsResultReturned(false) doesn't change the isResultReturned state to be false. Did I do something wrong?

I simplify the code to be like below.

// app/page.tsx
import ActionMenu from "./ActionItem";

export default function Home() {
  
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      
      <ActionMenu name="Test" />
    </main>
  );
}
// app/ActionItem.tsx


"use client"
import { useState } from 'react';
import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogHeader,
    DialogTitle,
    DialogTrigger,
    DialogFooter
} from "@/components/ui/dialog"

import {
    Button,
} from "@/components/ui/button"
import { Loader2 } from 'lucide-react';


export default function ActionMenu({ name }: { name: string }) {

    const [isResultReturned, setIsResultReturned] = useState(true)
    const deleteNoteGroup= () => {
        setIsResultReturned(false)
        setTimeout(() => {
            console.log('wait for 3 seconds')
            setIsResultReturned(true)
            console.log('check isResultReturned', isResultReturned)
        }, 3000)
    }

    function SharedDialog() {
        return (
            <Dialog>
                <DialogTrigger asChild>
                    <Button variant="outline">Delete</Button>
                </DialogTrigger>
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>Title</DialogTitle>
                        <DialogDescription>
                            Description
                        </DialogDescription>
                    </DialogHeader>
                    <DialogFooter>
                        <Button variant="destructive" onClick={deleteNoteGroup}>
                            {!isResultReturned && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                            Delete
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>
        )
    }

    return (
        <SharedDialog />
    )
}

Solution

  • As you build a separate function SharedDialog and returning it, changing in state setIsResultReturned will rerendering your SharedDialog that's why it gets closed

    Solution:

    // app/ActionItem.tsx
    
    
    "use client"
    import { useState } from 'react';
    import {
        Dialog,
        DialogContent,
        DialogDescription,
        DialogHeader,
        DialogTitle,
        DialogTrigger,
        DialogFooter
    } from "@/components/ui/dialog"
    
    import {
        Button,
    } from "@/components/ui/button"
    import { Loader2 } from 'lucide-react';
    
    
    export default function ActionMenu({ name }: { name: string }) {
    
        const [isResultReturned, setIsResultReturned] = useState(true)
        const deleteNoteGroup= () => {
            setIsResultReturned(false)
            setTimeout(() => {
                console.log('wait for 3 seconds')
                setIsResultReturned(true)
                console.log('check isResultReturned', isResultReturned)
            }, 3000)
        }
    
       
            return (
                <Dialog>
                    <DialogTrigger asChild>
                        <Button variant="outline">Delete</Button>
                    </DialogTrigger>
                    <DialogContent>
                        <DialogHeader>
                            <DialogTitle>Title</DialogTitle>
                            <DialogDescription>
                                Description
                            </DialogDescription>
                        </DialogHeader>
                        <DialogFooter>
                            <Button variant="destructive" onClick={deleteNoteGroup}>
                                {!isResultReturned && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                                Delete
                            </Button>
                        </DialogFooter>
                    </DialogContent>
                </Dialog>
            )
    }
    

    Changes: Returns a Dialog directly