@@ -352,3 +352,107 @@ test.describe("useFetcher", () => {
352
352
) ;
353
353
} ) ;
354
354
} ) ;
355
+
356
+ test . describe ( "fetcher aborts and adjacent forms" , ( ) => {
357
+ let fixture : Fixture ;
358
+ let appFixture : AppFixture ;
359
+
360
+ test . beforeAll ( async ( ) => {
361
+ fixture = await createFixture ( {
362
+ future : {
363
+ v2_routeConvention : true ,
364
+ } ,
365
+ files : {
366
+ "app/routes/_index.jsx" : js `
367
+ import * as React from "react";
368
+ import {
369
+ Form,
370
+ useFetcher,
371
+ useLoaderData,
372
+ useNavigation
373
+ } from "@remix-run/react";
374
+
375
+ export async function loader({ request }) {
376
+ // 1 second timeout on data
377
+ await new Promise((r) => setTimeout(r, 1000));
378
+ return { foo: 'bar' };
379
+ }
380
+
381
+ export default function Index() {
382
+ const [open, setOpen] = React.useState(true);
383
+ const { data } = useLoaderData();
384
+ const navigation = useNavigation();
385
+
386
+ return (
387
+ <div>
388
+ {navigation.state === 'idle' && <div id="idle">Idle</div>}
389
+ <Form id="main-form">
390
+ <input id="submit-form" type="submit" />
391
+ </Form>
392
+
393
+ <button id="open" onClick={() => setOpen(true)}>Show async form</button>
394
+ {open && <Child onClose={() => setOpen(false)} />}
395
+ </div>
396
+ );
397
+ }
398
+
399
+ function Child({ onClose }) {
400
+ const fetcher = useFetcher();
401
+
402
+ return (
403
+ <fetcher.Form method="get" action="/api">
404
+ <button id="submit-fetcher" type="submit">Trigger fetcher (shows a message)</button>
405
+ <button
406
+ type="submit"
407
+ form="main-form"
408
+ id="submit-and-close"
409
+ onClick={() => setTimeout(onClose, 250)}
410
+ >
411
+ Submit main form and close async form
412
+ </button>
413
+ </fetcher.Form>
414
+ );
415
+ }
416
+ ` ,
417
+
418
+ "app/routes/api.jsx" : js `
419
+ export async function loader() {
420
+ await new Promise((resolve) => setTimeout(resolve, 500));
421
+ return { message: 'Hello world!' }
422
+ }
423
+ ` ,
424
+ } ,
425
+ } ) ;
426
+
427
+ appFixture = await createAppFixture ( fixture ) ;
428
+ } ) ;
429
+
430
+ test . afterAll ( ( ) => {
431
+ appFixture . close ( ) ;
432
+ } ) ;
433
+
434
+ test ( "Unmounting a fetcher does not cancel the request of an adjacent form" , async ( {
435
+ page,
436
+ } ) => {
437
+ let app = new PlaywrightFixture ( appFixture , page ) ;
438
+ await app . goto ( "/" ) ;
439
+
440
+ // Works as expected before the fetcher is loaded
441
+
442
+ // submit the main form and unmount the fetcher form
443
+ await app . clickElement ( "#submit-and-close" ) ;
444
+ // Wait for our navigation state to be "Idle"
445
+ await page . waitForSelector ( "#idle" , { timeout : 2000 } ) ;
446
+
447
+ // Breaks after the fetcher is loaded
448
+
449
+ // re-mount the fetcher form
450
+ await app . clickElement ( "#open" ) ;
451
+ // submit the fetcher form
452
+ await app . clickElement ( "#submit-fetcher" ) ;
453
+ // submit the main form and unmount the fetcher form
454
+ await app . clickElement ( "#submit-and-close" ) ;
455
+ // Wait for navigation state to be "Idle"
456
+ await page . waitForSelector ( "#idle" , { timeout : 2000 } ) ;
457
+ } ) ;
458
+ } ) ;
0 commit comments