diff --git a/package-lock.json b/package-lock.json
index 26e55338..1d0714ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
"name": "react-app-starter",
"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",
@@ -776,14 +778,73 @@
"@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,
+ "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": "opencollective",
- "url": "https://opencollective.com/popperjs"
+ "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",
+ "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/@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": {
@@ -972,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",
@@ -1167,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",
@@ -1344,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",
@@ -1403,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",
@@ -1475,6 +1577,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..640713ba 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,8 @@
"preview": "vite preview"
},
"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/App.tsx b/src/App.tsx
index 08f92be1..17976e64 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,15 @@
-import './App.css';
+import "./App.css";
+import PostList from "./react-query/PostList";
+import TodoForm from "./react-query/TodoForm";
+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..717e1b36 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,13 +1,26 @@
-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 { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+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({
+ 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..cb4e0bda 100644
--- a/src/react-query/PostList.tsx
+++ b/src/react-query/PostList.tsx
@@ -1,34 +1,43 @@
-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";
+import React from "react";
const PostList = () => {
- const [posts, setPosts] = useState([]);
- const [error, setError] = useState('');
+ const pageSize = 10;
- useEffect(() => {
- axios
- .get('https://jsonplaceholder.typicode.com/posts')
- .then((res) => setPosts(res.data))
- .catch((error) => setError(error));
- }, []);
+ const {
+ data: posts,
+ error,
+ isLoading,
+ fetchNextPage,
+ isFetchingNextPage,
+ } = usePosts({ pageSize });
- if (error) return {error}
;
+ 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/TodoForm.tsx b/src/react-query/TodoForm.tsx
index 511cc688..419cc9a1 100644
--- a/src/react-query/TodoForm.tsx
+++ b/src/react-query/TodoForm.tsx
@@ -1,17 +1,39 @@
-import { useRef } from 'react';
+import { useRef } from "react";
+import useAddTodo from "./hooks/useAddTodo";
const TodoForm = () => {
const ref = useRef(null);
+ const addTodo = useAddTodo(() => {
+ if (ref.current) ref.current.value = "";
+ });
return (
-
+ <>
+ {addTodo.error && (
+ {addTodo.error.message}
+ )}
+
+ >
);
};
diff --git a/src/react-query/TodoList.tsx b/src/react-query/TodoList.tsx
index e397595d..0a44f621 100644
--- a/src/react-query/TodoList.tsx
+++ b/src/react-query/TodoList.tsx
@@ -1,6 +1,4 @@
-import axios from 'axios';
-import React, { useEffect, useState } from 'react';
-
+import useTodos from "./hooks/useTodos";
interface Todo {
id: number;
title: string;
@@ -9,21 +7,14 @@ interface Todo {
}
const TodoList = () => {
- const [todos, setTodos] = useState([]);
- const [error, setError] = useState('');
-
- useEffect(() => {
- axios
- .get('https://jsonplaceholder.typicode.com/todos')
- .then((res) => setTodos(res.data))
- .catch((error) => setError(error));
- }, []);
+ const { data: todos, error, isLoading } = useTodos();
- if (error) return {error}
;
+ if (isLoading) return Loding...
;
+ if (error) return {error.message}
;
return (
- {todos.map((todo) => (
+ {todos?.map((todo) => (
-
{todo.title}
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/usePosts.ts b/src/react-query/hooks/usePosts.ts
new file mode 100644
index 00000000..cf64e128
--- /dev/null
+++ b/src/react-query/hooks/usePosts.ts
@@ -0,0 +1,33 @@
+import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
+import axios from "axios";
+interface Post {
+ id: number;
+ title: string;
+ body: string;
+ userId: number;
+}
+interface Props {
+ pageSize: number;
+}
+const usePosts = (query: Props) => {
+ const fetchPosts = ({ pageParam = 1 }) =>
+ axios
+ .get("https://jsonplaceholder.typicode.com/posts", {
+ params: {
+ _start: (pageParam - 1) * query.pageSize,
+ _limit: query.pageSize,
+ },
+ })
+ .then((res) => res.data);
+
+ return useInfiniteQuery({
+ queryKey: ["posts", query],
+ queryFn: fetchPosts,
+ keepPreviousData: true,
+ getNextPageParam(lastPage, allPages) {
+ return lastPage.length > 0 ? allPages.length + 1 : undefined;
+ },
+ });
+};
+
+export default usePosts;
diff --git a/src/react-query/hooks/useTodos.ts b/src/react-query/hooks/useTodos.ts
new file mode 100644
index 00000000..3b031d5a
--- /dev/null
+++ b/src/react-query/hooks/useTodos.ts
@@ -0,0 +1,13 @@
+import { useQuery } from "@tanstack/react-query";
+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: 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");