Search code examples
reactjsnext.js

How to show a loading spinner after using router.push() in next.js?


I am using "react-hook-form" and shadcn. I want to show a loading spinner when the user sends the form. The new data is fetched on the page.tsx each time the query changes. This process takes some time and I want to give the user a feedback, that he has to wait with a loading spinner.

What I tried so far:

  • pending (with useFormStatus() from react-dom)
  • form.formState.isSubmitting (with useForm() from react-hook-form)
  • loading - setLoading (with useState() from react)

Minimal example:

export function DatePickerForm() {
  const router = useRouter();
  const { from } = useDateRange(); // just receives the from date from the searchParams

  const form = useForm<z.infer<typeof FormSchema>>();

  async function onSubmit(data: z.infer<typeof FormSchema>) {
    const from = format(data.from, "yyyy-MM-dd");
    router.push(`/?from=${from}`);
  }

  return (
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
          <div className="flex flex-row space-x-4">
            <FormField
              control={form.control}
              name="from"
              render={({ field }) => (
                <FormItem className="flex flex-col">
                  <FormLabel>Von</FormLabel>
                  <Popover>
                    <PopoverTrigger asChild>
                      <FormControl>
                        <Button
                          variant={"outline"}
                          className={cn(
                            "w-[240px] pl-3 text-left font-normal",
                            !field.value && "text-muted-foreground"
                          )}
                        >
                          {field.value ? (
                            format(field.value, "PPP", { locale: de })
                          ) : (
                            <span>Pick date</span>
                          )}
                          <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
                        </Button>
                      </FormControl>
                    </PopoverTrigger>

                    <PopoverContent className="w-auto p-0" align="start">
                      <Calendar
                        mode="single"
                        selected={field.value}
                        onSelect={field.onChange}
                        disabled={(date) =>
                          date > new Date() || date < new Date("1900-01-01")
                        }
                        initialFocus
                      />
                    </PopoverContent>
                  </Popover>
                  <FormMessage />
                </FormItem>
              )}
            />

          </div>

          <Button disabled={//HELP HERE!} type="submit">
            {/* //HELP HERE! <Loader2 className="mr-2 h-4 w-4 animate-spin" /> */}
            Suchen
          </Button>
        </form>
      </Form>
  );
}


Solution

  • You can use useTransition hook:

    import { useRouter } from 'next/navigation'
    import { useTransition } from 'react'
    
    'use client'
    
    export function MyComponent() {
      const router = useRouter()
      const [isPending, startTransition] = useTransition()
    
      function navigate() {
        startTransition(() => {
          router.push('/sometwhere')
        })
      }
    
      return (
        <div>
          <button type="button" onClick={navigate}>Click</button>
          {isPending && <p>Navigating...</p>}
        </div>
      )
    }