diff --git a/client/src/components/shared/Toast.test.tsx b/client/src/components/shared/Toast.test.tsx index ca11549c8..b9bd1ab30 100644 --- a/client/src/components/shared/Toast.test.tsx +++ b/client/src/components/shared/Toast.test.tsx @@ -1,6 +1,7 @@ import { render, screen, act } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; -import { ToastContainer } from './Toast'; +import { renderHook } from '@testing-library/react'; +import { ToastContainer, useToast } from './Toast'; describe('ToastContainer', () => { beforeEach(() => { @@ -92,3 +93,12 @@ describe('ToastContainer', () => { expect(screen.getByText('Third toast')).toBeTruthy(); }); }); + +describe('useToast', () => { + it('hands back the same object on every render, so effects that list it do not re-run', () => { + const { result, rerender } = renderHook(() => useToast()); + const first = result.current; + rerender(); + expect(result.current).toBe(first); + }); +}); diff --git a/client/src/components/shared/Toast.tsx b/client/src/components/shared/Toast.tsx index 872b3f9b3..0fede711a 100644 --- a/client/src/components/shared/Toast.tsx +++ b/client/src/components/shared/Toast.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react' +import React, { useMemo } from 'react' import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-react' import { useToastQueue, type ToastType } from './useToastQueue' @@ -101,19 +101,21 @@ export function ToastContainer() { ) } -export const useToast = () => { - const show = useCallback((message: string, type: ToastType, duration?: number) => { - if (window.__addToast) { - window.__addToast(message, type, duration) +// One object for the life of the component: hooks list `toast` in their effect +// dependencies, and a fresh object per render re-ran those effects on every keystroke. +export const useToast = () => + useMemo(() => { + const show = (message: string, type: ToastType, duration?: number) => { + if (window.__addToast) { + window.__addToast(message, type, duration) + } + } + return { + success: (message: string, duration?: number) => show(message, 'success', duration), + error: (message: string, duration?: number) => show(message, 'error', duration), + warning: (message: string, duration?: number) => show(message, 'warning', duration), + info: (message: string, duration?: number) => show(message, 'info', duration), } }, []) - return { - success: (message: string, duration?: number) => show(message, 'success', duration), - error: (message: string, duration?: number) => show(message, 'error', duration), - warning: (message: string, duration?: number) => show(message, 'warning', duration), - info: (message: string, duration?: number) => show(message, 'info', duration), - } -} - export default useToast