├── .gitignore ├── README.md ├── db.json ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── App.jsx ├── api │ └── posts.jsx ├── components │ ├── AddPost.jsx │ └── PostForm.jsx ├── main.jsx └── pages │ ├── EditPost.jsx │ ├── Post.jsx │ └── PostLists.jsx └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project). 2 | 3 | 4 | ## Available Scripts 5 | 6 | In the project directory, you can run: 7 | 8 | ### `npm install` 9 | 10 | Instal All dependencies in this project 11 | 12 | ### `npm run dev` 13 | 14 | Runs the app in the development mode.
15 | Open [http://127.0.0.1:5173](http://127.0.0.1:5173) to view it in the browser. 16 | 17 | ### Link 18 | 19 | - Tanstack react-query: https://tanstack.com/query 20 | - vite: https://vitejs.dev 21 | - json-server: https://github.com/typicode/json-server 22 | - uuid: https://github.com/uuidjs/uuid 23 | 24 | ### Video Tutorial 25 | 26 | You can see my youtube video for this project in [here](https://youtu.be/AAMBoENvfnE) -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [] 3 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crud-react-query", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@tanstack/react-query": "^4.24.10", 13 | "@tanstack/react-query-devtools": "^4.24.12", 14 | "json-server": "^0.17.2", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.8.2", 18 | "uuid": "^9.0.0" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.0.27", 22 | "@types/react-dom": "^18.0.10", 23 | "@vitejs/plugin-react": "^3.1.0", 24 | "vite": "^4.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom" 2 | import EditPost from "./pages/EditPost" 3 | import Post from "./pages/Post" 4 | import PostLists from "./pages/PostLists" 5 | 6 | function App() { 7 | 8 | return ( 9 |
10 |

Awesome blog

11 | 12 | } /> 13 | } /> 14 | } /> 15 | 16 |
17 | ) 18 | } 19 | 20 | export default App 21 | -------------------------------------------------------------------------------- /src/api/posts.jsx: -------------------------------------------------------------------------------- 1 | export async function fetchPosts() { 2 | const response = await fetch('http://localhost:3000/posts'); 3 | return response.json() 4 | } 5 | 6 | export async function fetchPost(id) { 7 | const response = await fetch(`http://localhost:3000/posts/${id}`); 8 | return response.json() 9 | } 10 | 11 | export async function createPost(newPost) { 12 | const response = await fetch(`http://localhost:3000/posts`, { 13 | method: "POST", 14 | headers: { 15 | "Content-Type": "application/json" 16 | }, 17 | body: JSON.stringify(newPost) 18 | }); 19 | return response.json() 20 | } 21 | 22 | export async function updatePost(updatedPost) { 23 | const response = await fetch(`http://localhost:3000/posts/${updatedPost.id}`, { 24 | method: "PUT", 25 | headers: { 26 | "Content-Type": "application/json" 27 | }, 28 | body: JSON.stringify(updatedPost) 29 | }); 30 | return response.json() 31 | } 32 | 33 | export async function deletePost(id) { 34 | const response = await fetch(`http://localhost:3000/posts/${id}`, { 35 | method: "DELETE", 36 | }); 37 | return response.json() 38 | } -------------------------------------------------------------------------------- /src/components/AddPost.jsx: -------------------------------------------------------------------------------- 1 | import { useMutation, useQueryClient } from "@tanstack/react-query" 2 | import { createPost } from "../api/posts" 3 | import PostForm from "./PostForm" 4 | import { v4 as uuidv4 } from 'uuid'; 5 | 6 | const AddPost = () => { 7 | const queryClient = useQueryClient(); 8 | 9 | const createPostMutation = useMutation({ 10 | mutationFn: createPost, 11 | onSuccess: () => { 12 | queryClient.invalidateQueries({ queryKey: ['posts']}); 13 | console.log("success bro!") 14 | } 15 | }); 16 | 17 | const handleAddPost = (post) => { 18 | createPostMutation.mutate({ 19 | id: uuidv4(), 20 | ...post 21 | }) 22 | } 23 | 24 | return ( 25 |
26 |

Add new post

27 | 28 |
29 | ) 30 | } 31 | 32 | export default AddPost -------------------------------------------------------------------------------- /src/components/PostForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const PostForm = ({ onSubmit, initialValue }) => { 4 | const [post, setPost] = useState({ 5 | title: initialValue.title || "", 6 | body: initialValue.body || "" 7 | }); 8 | 9 | const handleChangeInput = (e) => { 10 | setPost({ 11 | ...post, 12 | [e.target.name]: e.target.value 13 | }) 14 | } 15 | 16 | const renderField = (label) => ( 17 |
18 | 19 | 20 |
21 | ); 22 | 23 | const handleSubmit = (e) => { 24 | e.preventDefault(); 25 | onSubmit(post); 26 | setPost({ 27 | title: "", 28 | body: "" 29 | }) 30 | 31 | } 32 | 33 | return ( 34 |
35 | {renderField('Title')} 36 | {renderField('Body')} 37 | 38 |
39 | ) 40 | } 41 | 42 | export default PostForm -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 3 | import React from "react"; 4 | import ReactDOM from "react-dom/client"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | import App from "./App"; 7 | 8 | // create a client 9 | const queryClient = new QueryClient(); 10 | 11 | ReactDOM.createRoot(document.getElementById("root")).render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/pages/EditPost.jsx: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 | import { useNavigate, useParams } from "react-router-dom"; 3 | import { fetchPost, updatePost } from "../api/posts"; 4 | import PostForm from "../components/PostForm" 5 | 6 | const EditPost = () => { 7 | const queryClient = useQueryClient(); 8 | const navigate = useNavigate(); 9 | const { id } = useParams(); 10 | const { 11 | isLoading, 12 | isError, 13 | data: post, 14 | error, 15 | } = useQuery({ 16 | queryKey: ["posts", id], 17 | queryFn: () => fetchPost(id), 18 | }); 19 | const updatePostMutation = useMutation({ 20 | mutationFn: updatePost, 21 | onSuccess: () => { 22 | queryClient.invalidateQueries({ queryKey: ['posts']}); 23 | navigate("/") 24 | } 25 | }) 26 | 27 | if (isLoading) return "loading..."; 28 | if (isError) return `Error: ${error.message}`; 29 | 30 | const handleSubmit = (updatedPost) => { 31 | updatePostMutation.mutate({id, ...updatedPost}) 32 | } 33 | 34 | 35 | return ( 36 |
37 | 38 |
39 | ) 40 | } 41 | 42 | export default EditPost -------------------------------------------------------------------------------- /src/pages/Post.jsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useNavigate, useParams } from "react-router-dom"; 3 | import { fetchPost } from "../api/posts"; 4 | 5 | const Post = () => { 6 | const navigate = useNavigate(); 7 | const { id } = useParams(); 8 | const { 9 | isLoading, 10 | isError, 11 | data: post, 12 | error, 13 | } = useQuery({ 14 | queryKey: ["posts", id], 15 | queryFn: () => fetchPost(id), 16 | }); 17 | 18 | if (isLoading) return "loading..."; 19 | if (isError) return `Error: ${error.message}`; 20 | 21 | 22 | return ( 23 |
24 | 25 |

{post.title}

26 |

{post.body}

27 |
28 | ) 29 | } 30 | 31 | export default Post -------------------------------------------------------------------------------- /src/pages/PostLists.jsx: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { deletePost, fetchPosts } from "../api/posts"; 4 | import AddPost from "../components/AddPost"; 5 | 6 | const PostLists = () => { 7 | const navigate = useNavigate(); 8 | const queryClient = useQueryClient(); 9 | 10 | const { 11 | isLoading, 12 | isError, 13 | data: posts, 14 | error, 15 | } = useQuery({ 16 | queryKey: ["posts"], 17 | queryFn: fetchPosts, 18 | }); 19 | 20 | const deletePostMutation = useMutation({ 21 | mutationFn: deletePost, 22 | onSuccess: () => { 23 | queryClient.invalidateQueries({ queryKey: ['posts']}); 24 | } 25 | }); 26 | 27 | const handleDelete = (id) => { 28 | deletePostMutation.mutate(id) 29 | } 30 | 31 | if (isLoading) return "loading..."; 32 | if (isError) return `Error: ${error.message}`; 33 | 34 | return ( 35 |
36 | 37 | {posts.map((post) => ( 38 |
39 |

navigate(`/post/${post.id}`)} 42 | > 43 | {post.title} 44 |

45 | 46 | 47 |
48 | ))} 49 |
50 | ); 51 | }; 52 | 53 | export default PostLists; 54 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------