diff --git a/.gitignore b/.gitignore index fd3dbb5..9a6b72c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/.idea diff --git a/app/components/AddHotelForm.tsx b/app/components/AddHotelForm.tsx index c880e8f..0c6b88e 100644 --- a/app/components/AddHotelForm.tsx +++ b/app/components/AddHotelForm.tsx @@ -1,6 +1,7 @@ "use client"; import * as React from "react"; import { Button, Paper, Stack, TextInput, Title } from "@mantine/core"; +import {useEffect} from "react"; export interface HotelFormValues { name: string; @@ -28,6 +29,14 @@ export function AddHotelForm(props: AddHotelFormProps) { } }; + useEffect(() => { + if (!isSubmitting) { + setName(""); + setDescription("Lorem ipsum description."); + setPrice("7999"); + } + }, [isSubmitting]); + return ( Add new hotel diff --git a/app/components/HotelsGridInfinite.tsx b/app/components/HotelsGridInfinite.tsx index a9d67db..aff23f2 100644 --- a/app/components/HotelsGridInfinite.tsx +++ b/app/components/HotelsGridInfinite.tsx @@ -1,9 +1,11 @@ "use client"; import * as React from "react"; import { Button, Center, Loader, SimpleGrid, Stack, Text } from "@mantine/core"; -import { useQuery } from "@tanstack/react-query"; +import {useInfiniteQuery, useQuery} from "@tanstack/react-query"; import { HotelDTO } from "../types"; import { Hotel } from "./Hotel"; +import {useIntersection} from "@mantine/hooks"; +import {useEffect} from "react"; export function HotelsGridInfinite() { // @TODO: implement infinite scroll using useInfiniteQuery @@ -14,17 +16,31 @@ export function HotelsGridInfinite() { // next: number; // }> // read more: https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries - const { data, isPending, isError } = useQuery({ + const { entry, ref } = useIntersection(); + const { data, isPending, isError, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ["hotels-pages"], - queryFn: async () => { - const res = await fetch(`http://localhost:8080/hotels`); + queryFn: async ({ + pageParam, + }): Promise<{ + data: HotelDTO[]; + next: number; + }> => { + const res = await fetch(`http://localhost:8080/hotels?_page=${pageParam}&_per_page=5`); if (!res.ok) { throw new Error("Failed to fetch"); } return await res.json(); }, + initialPageParam: 1, + getNextPageParam: (lastPage) => lastPage.next, }); + useEffect(() => { + if (entry?.isIntersecting && hasNextPage) { + fetchNextPage(); + } + }, [entry?.isIntersecting, hasNextPage, fetchNextPage]); + if (isError) { return Something went wrong; } @@ -41,14 +57,15 @@ export function HotelsGridInfinite() { return ( <> - {data?.map(({ id, name, description, price }) => ( + {data?.pages.flatMap(({data}) => data).map(({ id, name, description, price }) => ( ))} - + */} ); } diff --git a/app/use-mutation/page.tsx b/app/use-mutation/page.tsx index 162ff73..48a7466 100644 --- a/app/use-mutation/page.tsx +++ b/app/use-mutation/page.tsx @@ -1,20 +1,38 @@ "use client"; import * as React from "react"; import { Stack } from "@mantine/core"; -import { useMutation } from "@tanstack/react-query"; -import { AddHotelForm } from "@/app/components/AddHotelForm"; +import {useMutation, useQueryClient} from "@tanstack/react-query"; +import {AddHotelForm, HotelFormValues} from "@/app/components/AddHotelForm"; import { HotelsGrid } from "@/app/components/HotelsGrid"; +import {delay} from "@/app/utils"; export default function Page() { // @TODO: post a new hotel using useMutation // POST http://localhost:8080/hotels // Add optimistic update. Read more: https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates - - useMutation({}); + const queryClient = useQueryClient(); + const { mutate, isPending } = useMutation({ + mutationKey: ["addHotel"], + mutationFn: async (values: HotelFormValues) => { + await delay(); + await fetch('http://localhost:8080/hotels', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + }, + onSettled: async () => { + return await queryClient.invalidateQueries({ + queryKey: ['hotels'], + }); + } + }); return ( - console.log({ values })} /> + mutate(values)} isSubmitting={isPending} /> ); diff --git a/app/use-query/page.tsx b/app/use-query/page.tsx index 29fa2d8..71e78c6 100644 --- a/app/use-query/page.tsx +++ b/app/use-query/page.tsx @@ -3,46 +3,16 @@ import * as React from "react"; import { Center, Loader, SimpleGrid, Stack, Text } from "@mantine/core"; import { Hotel } from "@/app/components/Hotel"; import { HotelDTO } from "@/app/types"; +import {useQuery} from "@tanstack/react-query"; + +const fetchHotels = async () => { + const res = await fetch("http://localhost:8080/hotels"); + return await res.json(); +}; export default function Page() { - // @TODO: Refactor this code to useQuery - // GET http://localhost:8080/hotels - const [isPending, setIsPending] = React.useState(true); - const [data, setData] = React.useState(); - const [isError, setIsError] = React.useState(); + const { data, isError, isPending } = useQuery({queryKey: ["hotels"], queryFn: fetchHotels}); - React.useEffect(() => { - // Use `ignore` flag to avoid race conditions - let ignore = false; - setIsPending(true); - fetch(`http://localhost:8080/hotels`) - .then((res) => { - if (!res.ok) { - throw new Error("Failed to fetch"); - } - return res.json(); - }) - .then((d) => { - if (!ignore) { - setData(d); - setIsError(undefined); - } - }) - .catch((e) => { - if (!ignore) { - setIsError(e); - setData(undefined); - } - }) - .finally(() => { - if (!ignore) { - setIsPending(false); - } - }); - return () => { - ignore = true; - }; - }, []); if (isError) { return Something went wrong;