From b0ad28e41d2966d72ab8ba1e8ca8a812a0b06e2a Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Thu, 18 Apr 2024 15:57:42 +0530 Subject: [PATCH 01/10] initial --- package-lock.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26e55338..8b3d1fed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -776,16 +776,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", From cdc0d29990ea2b1b967a780166fed1bf698946e6 Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 11:45:08 +0530 Subject: [PATCH 02/10] Adding Rect query,Hadling error and Loading --- package-lock.json | 44 ++++++++++++++++++++++++++++++++++++ package.json | 1 + src/App.tsx | 5 ++-- src/main.tsx | 21 +++++++++-------- src/react-query/TodoList.tsx | 32 ++++++++++++++++---------- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b3d1fed..20f19e63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "react-app-starter", "version": "0.0.0", "dependencies": { + "@tanstack/react-query": "4.28", "axios": "^1.3.4", "bootstrap": "^5.2.3", "react": "^18.2.0", @@ -776,6 +777,41 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@tanstack/query-core": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.27.0.tgz", + "integrity": "sha512-sm+QncWaPmM73IPwFlmWSKPqjdTXZeFf/7aEmWh00z7yl2FjqophPt0dE1EHW9P1giMC5rMviv7OUbSDmWzXXA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.28.0.tgz", + "integrity": "sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==", + "dependencies": { + "@tanstack/query-core": "4.27.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1465,6 +1501,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz", diff --git a/package.json b/package.json index c0724f30..c7ffff81 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "4.28", "axios": "^1.3.4", "bootstrap": "^5.2.3", "react": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 08f92be1..eba95d75 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,8 @@ -import './App.css'; +import "./App.css"; +import TodoList from "./react-query/TodoList"; function App() { - return

React Starter Project

; + return ; } export default App; diff --git a/src/main.tsx b/src/main.tsx index c7edb443..02dc5fc6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,13 +1,16 @@ -import 'bootstrap/dist/css/bootstrap.css'; -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; -import './index.css'; +import "bootstrap/dist/css/bootstrap.css"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import App from "./App"; +import "./index.css"; -ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -).render( +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + + + ); diff --git a/src/react-query/TodoList.tsx b/src/react-query/TodoList.tsx index e397595d..ee8b3af2 100644 --- a/src/react-query/TodoList.tsx +++ b/src/react-query/TodoList.tsx @@ -1,5 +1,6 @@ -import axios from 'axios'; -import React, { useEffect, useState } from 'react'; +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; +import React, { useEffect, useState } from "react"; interface Todo { id: number; @@ -9,21 +10,28 @@ interface Todo { } const TodoList = () => { - const [todos, setTodos] = useState([]); - const [error, setError] = useState(''); - - useEffect(() => { + const fetchTodos = () => axios - .get('https://jsonplaceholder.typicode.com/todos') - .then((res) => setTodos(res.data)) - .catch((error) => setError(error)); - }, []); + .get("https://jsonplaceholder.typicode.com/todos") + .then((res) => res.data); + + const { + data: todos, + error, + isLoading, + } = useQuery({ + queryKey: ["todos"], + queryFn: fetchTodos, + }); + // const [todos, setTodos] = useState([]); + // const [error, setError] = useState(''); - if (error) return

{error}

; + if (isLoading) return

Loding...

; + if (error) return

{error.message}

; return (
    - {todos.map((todo) => ( + {todos?.map((todo) => (
  • {todo.title}
  • From ad4e7ec9ac097f5a47c6e5865d0fccca7b080ff1 Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 11:54:27 +0530 Subject: [PATCH 03/10] Create custom Query Hook --- src/react-query/TodoList.tsx | 21 ++------------------- src/react-query/hooks/useTodos.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 src/react-query/hooks/useTodos.ts diff --git a/src/react-query/TodoList.tsx b/src/react-query/TodoList.tsx index ee8b3af2..0a44f621 100644 --- a/src/react-query/TodoList.tsx +++ b/src/react-query/TodoList.tsx @@ -1,7 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; -import axios from "axios"; -import React, { useEffect, useState } from "react"; - +import useTodos from "./hooks/useTodos"; interface Todo { id: number; title: string; @@ -10,21 +7,7 @@ interface Todo { } const TodoList = () => { - const fetchTodos = () => - axios - .get("https://jsonplaceholder.typicode.com/todos") - .then((res) => res.data); - - const { - data: todos, - error, - isLoading, - } = useQuery({ - queryKey: ["todos"], - queryFn: fetchTodos, - }); - // const [todos, setTodos] = useState([]); - // const [error, setError] = useState(''); + const { data: todos, error, isLoading } = useTodos(); if (isLoading) return

    Loding...

    ; if (error) return

    {error.message}

    ; diff --git a/src/react-query/hooks/useTodos.ts b/src/react-query/hooks/useTodos.ts new file mode 100644 index 00000000..608015ac --- /dev/null +++ b/src/react-query/hooks/useTodos.ts @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; +interface Todo { + id: number; + title: string; + userId: number; + completed: boolean; +} +const useTodos = () => { + const fetchTodos = () => + axios + .get("https://jsonplaceholder.typicode.com/todos") + .then((res) => res.data); + + return useQuery({ + queryKey: ["todos"], + queryFn: fetchTodos, + }); +}; +export default useTodos; From 6ec464a95a834f901740c33d275079002b8d2188 Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 12:06:45 +0530 Subject: [PATCH 04/10] React Query Dev Tools --- package-lock.json | 76 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/main.tsx | 2 ++ 3 files changed, 79 insertions(+) diff --git a/package-lock.json b/package-lock.json index 20f19e63..1d0714ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tanstack/react-query": "4.28", + "@tanstack/react-query-devtools": "4.28", "axios": "^1.3.4", "bootstrap": "^5.2.3", "react": "^18.2.0", @@ -777,6 +778,21 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz", + "integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { "version": "4.27.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.27.0.tgz", @@ -812,6 +828,25 @@ } } }, + "node_modules/@tanstack/react-query-devtools": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.28.0.tgz", + "integrity": "sha512-1SnoMw1CWn8FdPEIHvlAzmMBX3heXJo11fyBtt+FzYAHj5yFC8P67Kpgi0HpLkY7SLnd6QK/7qFkpeH4AQbgZg==", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.28.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -998,6 +1033,20 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", @@ -1193,6 +1242,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1370,6 +1430,11 @@ "node": ">=0.10.0" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -1429,6 +1494,17 @@ "node": ">=0.10.0" } }, + "node_modules/superjson": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.13.3.tgz", + "integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index c7ffff81..640713ba 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tanstack/react-query": "4.28", + "@tanstack/react-query-devtools": "4.28", "axios": "^1.3.4", "bootstrap": "^5.2.3", "react": "^18.2.0", diff --git a/src/main.tsx b/src/main.tsx index 02dc5fc6..1f528f6a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,7 @@ import "bootstrap/dist/css/bootstrap.css"; import React from "react"; import ReactDOM from "react-dom/client"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; import "./index.css"; @@ -11,6 +12,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + ); From 2e44a124e378fd4fd63e896f04a402047f2c975d Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 12:58:18 +0530 Subject: [PATCH 05/10] Parameterized Queries --- src/App.tsx | 3 +- src/main.tsx | 10 +++++- src/react-query/PostList.tsx | 51 +++++++++++++++---------------- src/react-query/hooks/usePosts.ts | 25 +++++++++++++++ 4 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 src/react-query/hooks/usePosts.ts diff --git a/src/App.tsx b/src/App.tsx index eba95d75..debc94a7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,9 @@ import "./App.css"; +import PostList from "./react-query/PostList"; import TodoList from "./react-query/TodoList"; function App() { - return ; + return ; } export default App; diff --git a/src/main.tsx b/src/main.tsx index 1f528f6a..717e1b36 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,7 +6,15 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; import "./index.css"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 3, + cacheTime: 300_000, //5 min + staleTime: 10 * 1000, //10S + }, + }, +}); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/src/react-query/PostList.tsx b/src/react-query/PostList.tsx index d4158947..99a39213 100644 --- a/src/react-query/PostList.tsx +++ b/src/react-query/PostList.tsx @@ -1,34 +1,33 @@ -import axios from 'axios'; -import { useEffect, useState } from 'react'; - -interface Post { - id: number; - title: string; - body: string; - userId: number; -} +import axios from "axios"; +import { useEffect, useState } from "react"; +import usePosts from "./hooks/usePosts"; const PostList = () => { - const [posts, setPosts] = useState([]); - const [error, setError] = useState(''); - - useEffect(() => { - axios - .get('https://jsonplaceholder.typicode.com/posts') - .then((res) => setPosts(res.data)) - .catch((error) => setError(error)); - }, []); + const [userId, setUserId] = useState(); + const { data: posts, error, isLoading } = usePosts(userId); - if (error) return

    {error}

    ; + if (error) return

    {error.message}

    ; return ( -
      - {posts.map((post) => ( -
    • - {post.title} -
    • - ))} -
    + <> + +
      + {posts?.map((post) => ( +
    • + {post.title} +
    • + ))} +
    + ); }; diff --git a/src/react-query/hooks/usePosts.ts b/src/react-query/hooks/usePosts.ts new file mode 100644 index 00000000..8eeba9fa --- /dev/null +++ b/src/react-query/hooks/usePosts.ts @@ -0,0 +1,25 @@ +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; +interface Post { + id: number; + title: string; + body: string; + userId: number; +} +const usePosts = (userId: number | undefined) => { + const fetchPosts = () => + axios + .get("https://jsonplaceholder.typicode.com/posts", { + params: { + userId, + }, + }) + .then((res) => res.data); + + return useQuery({ + queryKey: userId ? ["users", userId, "posts"] : ["posts"], + queryFn: fetchPosts, + }); +}; + +export default usePosts; From 0eb6217ec37e9d51b1b9ea79d5c4a5f88534178c Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 13:23:32 +0530 Subject: [PATCH 06/10] Paginated Quries --- src/react-query/PostList.tsx | 30 ++++++++++++++++++------------ src/react-query/hooks/usePosts.ts | 12 +++++++++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/react-query/PostList.tsx b/src/react-query/PostList.tsx index 99a39213..be8a4ed3 100644 --- a/src/react-query/PostList.tsx +++ b/src/react-query/PostList.tsx @@ -3,23 +3,14 @@ import { useEffect, useState } from "react"; import usePosts from "./hooks/usePosts"; const PostList = () => { - const [userId, setUserId] = useState(); - const { data: posts, error, isLoading } = usePosts(userId); + const pageSize = 10; + const [page, setPage] = useState(1); + const { data: posts, error, isLoading } = usePosts({ page, pageSize }); if (error) return

    {error.message}

    ; return ( <> -
      {posts?.map((post) => (
    • @@ -27,6 +18,21 @@ const PostList = () => {
    • ))}
    + + + + ); }; diff --git a/src/react-query/hooks/usePosts.ts b/src/react-query/hooks/usePosts.ts index 8eeba9fa..181c3228 100644 --- a/src/react-query/hooks/usePosts.ts +++ b/src/react-query/hooks/usePosts.ts @@ -6,19 +6,25 @@ interface Post { body: string; userId: number; } -const usePosts = (userId: number | undefined) => { +interface Props { + page: number; + pageSize: number; +} +const usePosts = (query: Props) => { const fetchPosts = () => axios .get("https://jsonplaceholder.typicode.com/posts", { params: { - userId, + _start: (query.page - 1) * query.pageSize, + _limit: query.pageSize, }, }) .then((res) => res.data); return useQuery({ - queryKey: userId ? ["users", userId, "posts"] : ["posts"], + queryKey: ["posts", query], queryFn: fetchPosts, + keepPreviousData: true, }); }; From 232102372be87b44ea4ac9d74ede1b511ef0b0bc Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Fri, 19 Apr 2024 13:50:46 +0530 Subject: [PATCH 07/10] Infinite Queries --- src/react-query/PostList.tsx | 36 +++++++++++++++++-------------- src/react-query/hooks/usePosts.ts | 12 ++++++----- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/react-query/PostList.tsx b/src/react-query/PostList.tsx index be8a4ed3..cb4e0bda 100644 --- a/src/react-query/PostList.tsx +++ b/src/react-query/PostList.tsx @@ -1,37 +1,41 @@ import axios from "axios"; import { useEffect, useState } from "react"; import usePosts from "./hooks/usePosts"; +import React from "react"; const PostList = () => { const pageSize = 10; - const [page, setPage] = useState(1); - const { data: posts, error, isLoading } = usePosts({ page, pageSize }); + + const { + data: posts, + error, + isLoading, + fetchNextPage, + isFetchingNextPage, + } = usePosts({ pageSize }); if (error) return

    {error.message}

    ; return ( <>
      - {posts?.map((post) => ( -
    • - {post.title} -
    • + {posts?.pages.map((page, index) => ( + + {page?.map((post) => ( +
    • + {post.title} +
    • + ))} +
      ))}
    - - ); diff --git a/src/react-query/hooks/usePosts.ts b/src/react-query/hooks/usePosts.ts index 181c3228..cf64e128 100644 --- a/src/react-query/hooks/usePosts.ts +++ b/src/react-query/hooks/usePosts.ts @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import axios from "axios"; interface Post { id: number; @@ -7,24 +7,26 @@ interface Post { userId: number; } interface Props { - page: number; pageSize: number; } const usePosts = (query: Props) => { - const fetchPosts = () => + const fetchPosts = ({ pageParam = 1 }) => axios .get("https://jsonplaceholder.typicode.com/posts", { params: { - _start: (query.page - 1) * query.pageSize, + _start: (pageParam - 1) * query.pageSize, _limit: query.pageSize, }, }) .then((res) => res.data); - return useQuery({ + return useInfiniteQuery({ queryKey: ["posts", query], queryFn: fetchPosts, keepPreviousData: true, + getNextPageParam(lastPage, allPages) { + return lastPage.length > 0 ? allPages.length + 1 : undefined; + }, }); }; From 46fb713176256815b66c26d013ef7da6600e87dc Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Sat, 20 Apr 2024 12:16:58 +0530 Subject: [PATCH 08/10] Mutating_data and Handling mutation error --- src/App.tsx | 8 ++++- src/react-query/TodoForm.tsx | 58 ++++++++++++++++++++++++++----- src/react-query/hooks/useTodos.ts | 2 +- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index debc94a7..17976e64 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,15 @@ import "./App.css"; import PostList from "./react-query/PostList"; +import TodoForm from "./react-query/TodoForm"; import TodoList from "./react-query/TodoList"; function App() { - return ; + return ( + <> + + + + ); } export default App; diff --git a/src/react-query/TodoForm.tsx b/src/react-query/TodoForm.tsx index 511cc688..4bdded16 100644 --- a/src/react-query/TodoForm.tsx +++ b/src/react-query/TodoForm.tsx @@ -1,17 +1,57 @@ -import { useRef } from 'react'; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useRef } from "react"; +import { Todo } from "./hooks/useTodos"; +import axios from "axios"; const TodoForm = () => { + const queryClient = useQueryClient(); + const addTodo = useMutation({ + mutationFn: (todo: Todo) => + axios + .post("https://jsonplaceholder.typicode.com/xtodos", todo) + .then((res) => res.data), + onSuccess: (savedTodo, newTodo) => { + //Approach 1: Invalidating the cache + // queryClient.invalidateQueries({ + // queryKey:['todos'] + // }) + + //Approach 2: Updating the data in cache + queryClient.setQueriesData(["todos"], (todos) => [ + savedTodo, + ...(todos || []), + ]); + }, + }); const ref = useRef(null); return ( -
    -
    - -
    -
    - -
    -
    + <> + {addTodo.error && ( +
    {addTodo.error.message}
    + )} +
    { + event.preventDefault(); + + if (ref.current && ref.current.value) + addTodo.mutate({ + id: 0, + title: ref.current?.value, + completed: false, + userId: 1, + }); + }} + > +
    + +
    +
    + +
    +
    + ); }; diff --git a/src/react-query/hooks/useTodos.ts b/src/react-query/hooks/useTodos.ts index 608015ac..ff95e4bf 100644 --- a/src/react-query/hooks/useTodos.ts +++ b/src/react-query/hooks/useTodos.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import axios from "axios"; -interface Todo { +export interface Todo { id: number; title: string; userId: number; From 2f7b9fb5471c0fb492e98b8544c1add85b2b8225 Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Sat, 20 Apr 2024 13:57:02 +0530 Subject: [PATCH 09/10] Optimistic update Mutation --- src/react-query/TodoForm.tsx | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/react-query/TodoForm.tsx b/src/react-query/TodoForm.tsx index 4bdded16..c6b54184 100644 --- a/src/react-query/TodoForm.tsx +++ b/src/react-query/TodoForm.tsx @@ -2,14 +2,26 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRef } from "react"; import { Todo } from "./hooks/useTodos"; import axios from "axios"; +interface AddTodoContext { + previousTodos: Todo[]; +} const TodoForm = () => { const queryClient = useQueryClient(); - const addTodo = useMutation({ + const addTodo = useMutation({ mutationFn: (todo: Todo) => axios - .post("https://jsonplaceholder.typicode.com/xtodos", todo) + .post("https://jsonplaceholder.typicode.com/todosx", todo) .then((res) => res.data), + + onMutate: (newTodo: Todo) => { + const previousTodos = queryClient.getQueryData(["todos"]) || []; + queryClient.setQueriesData(["todos"], (todos) => [ + newTodo, + ...(todos || []), + ]); + return { previousTodos }; + }, onSuccess: (savedTodo, newTodo) => { //Approach 1: Invalidating the cache // queryClient.invalidateQueries({ @@ -17,10 +29,15 @@ const TodoForm = () => { // }) //Approach 2: Updating the data in cache - queryClient.setQueriesData(["todos"], (todos) => [ - savedTodo, - ...(todos || []), - ]); + queryClient.setQueriesData(["todos"], (todos) => + todos?.map((todo) => (todo === newTodo ? savedTodo : todo)) + ); + if (ref.current) ref.current.value = ""; + }, + onError: (error, newTodo, context) => { + if (!context) return; + + queryClient.setQueriesData(["todos"], context.previousTodos); }, }); const ref = useRef(null); From 9ebabb1011e6a715236b0864d26eeb5b434dee77 Mon Sep 17 00:00:00 2001 From: Karthick Ganesan Date: Sat, 20 Apr 2024 16:02:45 +0530 Subject: [PATCH 10/10] React Mutation final change --- src/react-query/TodoForm.tsx | 43 +++--------------------- src/react-query/constants.ts | 1 + src/react-query/hooks/useAddTodo.ts | 44 +++++++++++++++++++++++++ src/react-query/hooks/useTodos.ts | 21 ++++-------- src/react-query/services/apiClient.ts | 23 +++++++++++++ src/react-query/services/todoService.ts | 8 +++++ 6 files changed, 87 insertions(+), 53 deletions(-) create mode 100644 src/react-query/constants.ts create mode 100644 src/react-query/hooks/useAddTodo.ts create mode 100644 src/react-query/services/apiClient.ts create mode 100644 src/react-query/services/todoService.ts diff --git a/src/react-query/TodoForm.tsx b/src/react-query/TodoForm.tsx index c6b54184..419cc9a1 100644 --- a/src/react-query/TodoForm.tsx +++ b/src/react-query/TodoForm.tsx @@ -1,46 +1,11 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRef } from "react"; -import { Todo } from "./hooks/useTodos"; -import axios from "axios"; -interface AddTodoContext { - previousTodos: Todo[]; -} +import useAddTodo from "./hooks/useAddTodo"; const TodoForm = () => { - const queryClient = useQueryClient(); - const addTodo = useMutation({ - mutationFn: (todo: Todo) => - axios - .post("https://jsonplaceholder.typicode.com/todosx", todo) - .then((res) => res.data), - - onMutate: (newTodo: Todo) => { - const previousTodos = queryClient.getQueryData(["todos"]) || []; - queryClient.setQueriesData(["todos"], (todos) => [ - newTodo, - ...(todos || []), - ]); - return { previousTodos }; - }, - onSuccess: (savedTodo, newTodo) => { - //Approach 1: Invalidating the cache - // queryClient.invalidateQueries({ - // queryKey:['todos'] - // }) - - //Approach 2: Updating the data in cache - queryClient.setQueriesData(["todos"], (todos) => - todos?.map((todo) => (todo === newTodo ? savedTodo : todo)) - ); - if (ref.current) ref.current.value = ""; - }, - onError: (error, newTodo, context) => { - if (!context) return; - - queryClient.setQueriesData(["todos"], context.previousTodos); - }, - }); const ref = useRef(null); + const addTodo = useAddTodo(() => { + if (ref.current) ref.current.value = ""; + }); return ( <> diff --git a/src/react-query/constants.ts b/src/react-query/constants.ts new file mode 100644 index 00000000..5fc6b7de --- /dev/null +++ b/src/react-query/constants.ts @@ -0,0 +1 @@ +export const CACHE_KEY_TODO = ["todos"]; diff --git a/src/react-query/hooks/useAddTodo.ts b/src/react-query/hooks/useAddTodo.ts new file mode 100644 index 00000000..ebb8c238 --- /dev/null +++ b/src/react-query/hooks/useAddTodo.ts @@ -0,0 +1,44 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { CACHE_KEY_TODO } from "../constants"; +import APIClient from "../services/apiClient"; +import todoService, { Todo } from "../services/todoService"; +interface AddTodoContext { + previousTodos: Todo[]; +} + +const useAddTodo = (onAdd: () => void) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: todoService.post, + + onMutate: (newTodo: Todo) => { + const previousTodos = + queryClient.getQueryData(CACHE_KEY_TODO) || []; + queryClient.setQueriesData(CACHE_KEY_TODO, (todos = []) => [ + newTodo, + ...todos, + ]); + return { previousTodos }; + }, + onSuccess: (savedTodo, newTodo) => { + //Approach 1: Invalidating the cache + // queryClient.invalidateQueries({ + // queryKey:['todos'] + // }) + + //Approach 2: Updating the data in cache + queryClient.setQueriesData(CACHE_KEY_TODO, (todos) => + todos?.map((todo) => (todo === newTodo ? savedTodo : todo)) + ); + onAdd(); + // + }, + onError: (error, newTodo, context) => { + if (!context) return; + + queryClient.setQueriesData(CACHE_KEY_TODO, context.previousTodos); + }, + }); +}; + +export default useAddTodo; diff --git a/src/react-query/hooks/useTodos.ts b/src/react-query/hooks/useTodos.ts index ff95e4bf..3b031d5a 100644 --- a/src/react-query/hooks/useTodos.ts +++ b/src/react-query/hooks/useTodos.ts @@ -1,20 +1,13 @@ import { useQuery } from "@tanstack/react-query"; -import axios from "axios"; -export interface Todo { - id: number; - title: string; - userId: number; - completed: boolean; -} -const useTodos = () => { - const fetchTodos = () => - axios - .get("https://jsonplaceholder.typicode.com/todos") - .then((res) => res.data); +import { CACHE_KEY_TODO } from "../constants"; +import APIClient from "../services/apiClient"; +import todoService, { Todo } from "../services/todoService"; +const apiClient = new APIClient("/todos"); +const useTodos = () => { return useQuery({ - queryKey: ["todos"], - queryFn: fetchTodos, + queryKey: CACHE_KEY_TODO, + queryFn: todoService.getAll, }); }; export default useTodos; diff --git a/src/react-query/services/apiClient.ts b/src/react-query/services/apiClient.ts new file mode 100644 index 00000000..d99c0625 --- /dev/null +++ b/src/react-query/services/apiClient.ts @@ -0,0 +1,23 @@ +import axios from "axios"; +import AppRouter from "next/dist/client/components/app-router"; + +const apiInstance = axios.create({ + baseURL: "https://jsonplaceholder.typicode.com", +}); + +class APIClient { + endpoint: string; + constructor(endpoint: string) { + this.endpoint = endpoint; + } + + getAll = () => { + return apiInstance.get(this.endpoint).then((res) => res.data); + }; + + post = (data: T) => { + return apiInstance.post(this.endpoint).then((res) => res.data); + }; +} + +export default APIClient; diff --git a/src/react-query/services/todoService.ts b/src/react-query/services/todoService.ts new file mode 100644 index 00000000..48e28938 --- /dev/null +++ b/src/react-query/services/todoService.ts @@ -0,0 +1,8 @@ +import APIClient from "./apiClient"; +export interface Todo { + id: number; + title: string; + userId: number; + completed: boolean; +} +export default new APIClient("/todos");