Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4c4cbbc
Setting up React Query
hykhan94 Dec 11, 2023
48e6574
Fetching Data
hykhan94 Dec 12, 2023
1bdc244
Showing Loading Indicator
hykhan94 Dec 12, 2023
95377e8
Creating a Custom Query Hook
hykhan94 Dec 12, 2023
4b8c021
Setting up React Query
hykhan94 Dec 12, 2023
8aa0e47
Merge branch 'main' of https://github.com/hykhan94/react-course-part2…
hykhan94 Dec 12, 2023
703f31b
Using React Query Dev Tools
hykhan94 Dec 12, 2023
46982ea
Exercise - Fetching Data
hykhan94 Dec 12, 2023
b92870b
Parametrized Queries
hykhan94 Dec 14, 2023
27082fa
Infinite Queries
hykhan94 Dec 14, 2023
18ce431
Mutating Data and Handling Errors
hykhan94 Dec 15, 2023
79a81cb
Showing Mutation Progress
hykhan94 Dec 15, 2023
47b6e30
Setting up React Query
hykhan94 Jan 10, 2024
9eecac4
Merge branch 'main' of https://github.com/hykhan94/react-course-part2…
hykhan94 Jan 10, 2024
9f1046c
Coming back to default
hykhan94 Jan 10, 2024
45d3ccc
Fetching Data
hykhan94 Jan 10, 2024
424db68
Handling errors
hykhan94 Jan 10, 2024
3b62956
Showing a loading indicator
hykhan94 Jan 10, 2024
4264279
Creating a Custom Query Hook
hykhan94 Jan 10, 2024
983a18e
NIL
hykhan94 Jan 11, 2024
86b4459
Customizing Query Settings
hykhan94 Jan 11, 2024
595f034
Parametrized Queries
hykhan94 Jan 11, 2024
420e713
Parametrized Queries
hykhan94 Jan 12, 2024
ce50fa5
Paginated Queries
hykhan94 Jan 12, 2024
0313d7c
Infinite Queries
hykhan94 Jan 12, 2024
9e23aca
Mutating Data
hykhan94 Jan 13, 2024
36f19af
Handling mutation errors
hykhan94 Jan 13, 2024
533a7ec
Showing mutation progress
hykhan94 Jan 13, 2024
b73b7e9
Optimistic Updates
hykhan94 Jan 13, 2024
5b9fbff
Creating a Custom Mutation Hooks
hykhan94 Jan 13, 2024
9ba24fa
Creating a Reusable API Client
hykhan94 Jan 13, 2024
328433f
Creating reusable HTTP service
hykhan94 Jan 13, 2024
2030014
Creating State logic with Reducer
hykhan94 Jan 18, 2024
8f70f8a
Creatig Complex Actions
hykhan94 Jan 20, 2024
1e2a505
Exercise - Working with Reducers
hykhan94 Jan 20, 2024
8ce8d48
Sharing state using React Context
hykhan94 Jan 20, 2024
44a0262
Exercise - Working with Context
hykhan94 Jan 21, 2024
4669d45
Creating a Custom Provider
hykhan94 Jan 21, 2024
c00a575
Creating a Hook to access Context
hykhan94 Jan 21, 2024
eb38670
Exercise - Creating a Provider
hykhan94 Jan 21, 2024
7b1bba4
Organizing Code for Scalability and Maintainability
hykhan94 Jan 21, 2024
1ce4549
Exercise - Organizing Code
hykhan94 Jan 21, 2024
8b6620c
Managing Application State with Zustand
hykhan94 Jan 21, 2024
559b563
Exercise - Working with Zustand
hykhan94 Jan 22, 2024
80fbf70
Preventing Unnecessary Renders with Selector
hykhan94 Jan 22, 2024
f30a1a7
Inspecting Store with Zustand DevTools
hykhan94 Jan 22, 2024
a278732
Setting up routing
hykhan94 Jan 23, 2024
c47a9a7
Navigate
hykhan94 Jan 24, 2024
86e609b
Passing data with Route Parameter
hykhan94 Jan 24, 2024
d5337c6
Getting data about current route
hykhan94 Jan 24, 2024
55c686e
Nested Routes
hykhan94 Jan 24, 2024
9237313
Exercise - Working with Nested Routes
hykhan94 Jan 24, 2024
de53df1
Styling Active Link
hykhan94 Jan 24, 2024
c25c712
Handling Errors
hykhan94 Jan 24, 2024
0e6034d
Private Routes
hykhan94 Jan 28, 2024
3d41e8f
Layout Routes
hykhan94 Jan 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
939 changes: 547 additions & 392 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
"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",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0",
"simple-zustand-devtools": "^1.1.0",
"zustand": "^4.3.7"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/node": "^20.11.5",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^4.9.3",
"vite": "^4.2.0"
Expand Down
14 changes: 12 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import './App.css';
import "./App.css";
import HomePage from "./state-management/HomePage";
import NavBar from "./state-management/NavBar";
import Counter from "./state-management/counter/Counter";
import { TaskProvider } from "./state-management/tasks";

function App() {
return <h1>React Starter Project</h1>;
return (
<TaskProvider>
<Counter />
<NavBar />
<HomePage />
</TaskProvider>
);
}

export default App;
40 changes: 40 additions & 0 deletions src/hooks/useAddTodo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";

import { CACHE_KEY_TODOS } from "../react-query/constants";
import APIClient from "../services/apiClient";
import { Todo } from "../services/todoService";

const apiClient = new APIClient<Todo>("/todos");

interface AddTodosContext {
previousTodo: Todo[];
}
const useAddTodo = (onAdd: () => void) => {
const queryClient = useQueryClient();
return useMutation<Todo, Error, Todo, AddTodosContext>({
mutationFn: apiClient.post,
onMutate: (newTodo: Todo) => {
const previousTodo =
queryClient.getQueryData<Todo[]>(CACHE_KEY_TODOS) || [];
queryClient.setQueriesData<Todo[]>(CACHE_KEY_TODOS, (todos) => [
newTodo,
...(todos || []),
]);
onAdd();

return { previousTodo };
},
onSuccess: (savedTodo: Todo, newTodo: Todo) => {
queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, (todos) =>
todos?.map((todo) => (todo === newTodo ? savedTodo : todo))
);
},
onError: (error: Error, newTodo: Todo, context: AddTodosContext) => {
if (!context) return;

queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, context.previousTodo);
},
});
};

export default useAddTodo;
33 changes: 33 additions & 0 deletions src/hooks/usePosts.ts
Original file line number Diff line number Diff line change
@@ -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 PostQuery {
pageSize: number;
}

const usePosts = (query: PostQuery) =>
useInfiniteQuery<Post[], Error>({
queryKey: ["posts", query],
queryFn: ({ pageParam = 1 }) =>
axios
.get("https://jsonplaceholder.typicode.com/posts", {
params: {
_start: (pageParam - 1) * query.pageSize,
_limit: query.pageSize,
},
})
.then((res) => res.data),
staleTime: 1 * 60 * 1000,
keepPreviousData: true,
getNextPageParam: (lastPage, allPages) => {
return lastPage.length > 0 ? allPages.length + 1 : undefined;
},
});
export default usePosts;
13 changes: 13 additions & 0 deletions src/hooks/useTodos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useQuery } from "@tanstack/react-query";
import { CACHE_KEY_TODOS } from "../react-query/constants";
import todoService, { Todo } from "../services/todoService";

const useTodos = () => {
return useQuery<Todo[], Error>({
queryKey: CACHE_KEY_TODOS,
queryFn: todoService.getAll,
staleTime: 10 * 1000,
});
};

export default useTodos;
25 changes: 16 additions & 9 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
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 App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import "./index.css";
import { RouterProvider } from "react-router-dom";
import router from "./routing/routes";

ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
).render(
const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools />
</QueryClientProvider>
</React.StrictMode>
);
58 changes: 33 additions & 25 deletions src/react-query/PostList.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import axios from 'axios';
import { useEffect, useState } from 'react';

interface Post {
id: number;
title: string;
body: string;
userId: number;
}
import usePosts from "../hooks/usePosts";
import React from "react";

const PostList = () => {
const [posts, setPosts] = useState<Post[]>([]);
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 <p>{error}</p>;
if (isLoading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;

return (
<ul className="list-group">
{posts.map((post) => (
<li key={post.id} className="list-group-item">
{post.title}
</li>
))}
</ul>
<>
<ul className="list-group">
{posts.pages.map((page) => (
<React.Fragment>
{page.map((post) => (
<li key={post.id} className="list-group-item">
{post.title}
</li>
))}
</React.Fragment>
))}
</ul>

<button
disabled={isFetchingNextPage}
onClick={() => fetchNextPage()}
className="btn btn-primary mt-3"
>
{isFetchingNextPage ? "Loading..." : "Load More"}
</button>
</>
);
};

Expand Down
41 changes: 32 additions & 9 deletions src/react-query/TodoForm.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
import { useRef } from 'react';
import { useRef } from "react";
import useAddTodo from "../hooks/useAddTodo";

const TodoForm = () => {
const addTodo = useAddTodo(() => {
if (ref.current) ref.current.value = "";
});
const ref = useRef<HTMLInputElement>(null);

return (
<form className="row mb-3">
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary">Add</button>
</div>
</form>
<>
{addTodo.error && (
<div className="alert-danger">{addTodo.error.message}</div>
)}

<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
if (ref.current && ref.current.value) {
addTodo.mutate({
id: 0,
title: ref.current?.value,
userId: 1,
completed: false,
});
}
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary">Add</button>
</div>
</form>
</>
);
};

Expand Down
25 changes: 5 additions & 20 deletions src/react-query/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import axios from 'axios';
import React, { useEffect, useState } from 'react';

interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}
import useTodos from "../hooks/useTodos";

const TodoList = () => {
const [todos, setTodos] = useState<Todo[]>([]);
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 <p>{error}</p>;
if (isLoading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;

return (
<ul className="list-group">
{todos.map((todo) => (
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
Expand Down
1 change: 1 addition & 0 deletions src/react-query/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CACHE_KEY_TODOS = ["todos"];
4 changes: 4 additions & 0 deletions src/routing/ContactPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { useNavigate } from "react-router-dom";

const ContactPage = () => {
const navigate = useNavigate();
return (
<form
onSubmit={(event) => {
event.preventDefault();
// Redirect the user to the home page
navigate("/");
}}
>
<button className="btn btn-primary">Submit</button>
Expand Down
6 changes: 5 additions & 1 deletion src/routing/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { isRouteErrorResponse, useRouteError } from "react-router-dom";

const ErrorPage = () => {
const error = useRouteError();

return (
<>
<h1>Oops...</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>{isRouteErrorResponse(error) ? "Invalid Page" : "Unexpected Error"}</p>
</>
);
};
Expand Down
7 changes: 5 additions & 2 deletions src/routing/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Link } from "react-router-dom";

const HomePage = () => {
return (
<>
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Incidunt, mollitia!
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Incidunt,
mollitia!
</p>
<Link to="/users">Users</Link>
<a href="/users">Users</a>
</>
);
Expand Down
7 changes: 5 additions & 2 deletions src/routing/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import NavBar from './NavBar';
import { Outlet } from "react-router-dom";
import NavBar from "./NavBar";

const Layout = () => {
return (
<>
<NavBar />
<div id="main"></div>
<div id="main">
<Outlet />
</div>
</>
);
};
Expand Down
Loading