diff --git a/tutorial-react-ts/src/App.tsx b/tutorial-react-ts/src/App.tsx index 3d7ded3..5559269 100644 --- a/tutorial-react-ts/src/App.tsx +++ b/tutorial-react-ts/src/App.tsx @@ -1,33 +1,11 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' import './App.css' +import BlogApp from './blog/blogApp' function App() { - const [count, setCount] = useState(0) return ( <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+ ) } diff --git a/tutorial-react-ts/src/blog/blogApp.tsx b/tutorial-react-ts/src/blog/blogApp.tsx new file mode 100644 index 0000000..2b6729f --- /dev/null +++ b/tutorial-react-ts/src/blog/blogApp.tsx @@ -0,0 +1,24 @@ +import { PostType } from "./definitions/PostType"; +import PostItem from "./ui/PostItem"; +import useHandlePost from "./hooks/useHandlePost"; + +function BlogApp() { + + const { posts, handleUpdate, handleDelete, handleAddPost } = useHandlePost(); + + return ( +
+ {posts.map((post: PostType) => ( + + ))} + +
); +} + +export default BlogApp + + diff --git a/tutorial-react-ts/src/blog/definitions/PostActionType.ts b/tutorial-react-ts/src/blog/definitions/PostActionType.ts new file mode 100644 index 0000000..8d327c9 --- /dev/null +++ b/tutorial-react-ts/src/blog/definitions/PostActionType.ts @@ -0,0 +1,8 @@ +import { PostType } from "./PostType"; + + + +export type PostActionType = { type: "SET_POSTS"; payload: PostType[]; } | +{ type: "ADD_POST"; payload: PostType; } | +{ type: "UPDATE_POST"; payload: { id: number; updatedFields: Partial; }; } | +{ type: "DELETE_POST"; payload: number; }; diff --git a/tutorial-react-ts/src/blog/definitions/PostItemProps.tsx b/tutorial-react-ts/src/blog/definitions/PostItemProps.tsx new file mode 100644 index 0000000..d409315 --- /dev/null +++ b/tutorial-react-ts/src/blog/definitions/PostItemProps.tsx @@ -0,0 +1,8 @@ +import { PostType } from "./PostType"; + + +export type PostItemProps = { + post: PostType; + handleUpdate: (id: number) => void; + handleDelete: (id: number) => void; +}; diff --git a/tutorial-react-ts/src/blog/definitions/PostType.tsx b/tutorial-react-ts/src/blog/definitions/PostType.tsx new file mode 100644 index 0000000..bd20633 --- /dev/null +++ b/tutorial-react-ts/src/blog/definitions/PostType.tsx @@ -0,0 +1,8 @@ + +export type PostType = { + userId: number; + id: number; + title: string; + body: string; +}; + diff --git a/tutorial-react-ts/src/blog/hooks/useHandlePost.tsx b/tutorial-react-ts/src/blog/hooks/useHandlePost.tsx new file mode 100644 index 0000000..eb14bd5 --- /dev/null +++ b/tutorial-react-ts/src/blog/hooks/useHandlePost.tsx @@ -0,0 +1,29 @@ +import usePostData from "./usePostData"; + +function useHandlePost() { + + const { posts,postFunctions } = usePostData() + + const handleAddPost = () => { + const newPost = { + title: 'Titolo', + userId: 1, + body: 'Paragrafo inserito' + }; + postFunctions.AddPost(newPost); + }; + + const handleUpdate = (id: number) => { + postFunctions.UpdatePost(id, { + title: 'Nuovo Titolo', + body: 'Contenuto aggiornato' + }); + }; + + const handleDelete = (id: number) => { + postFunctions.DeletePost(id); + }; + return { posts,handleUpdate, handleDelete, handleAddPost }; +} + +export default useHandlePost \ No newline at end of file diff --git a/tutorial-react-ts/src/blog/hooks/usePostData.ts b/tutorial-react-ts/src/blog/hooks/usePostData.ts new file mode 100644 index 0000000..01af650 --- /dev/null +++ b/tutorial-react-ts/src/blog/hooks/usePostData.ts @@ -0,0 +1,92 @@ +import { useEffect, useReducer } from "react"; +import {postReducer} from "../reducer/postReducer"; +import { PostType } from "../definitions/PostType"; + +function usePostData() { + const [posts,dispatch] = useReducer(postReducer,[]) + + useEffect(() => { + fetch("https://jsonplaceholder.typicode.com/posts") + .then((response) => response.json()) + .then((data) => dispatch({type:'SET_POSTS', payload:data})); + }, []); + + const postFunctions = { + AddPost: (newPost: Omit) => { + fetch("https://jsonplaceholder.typicode.com/posts", { + method: "POST", + body: JSON.stringify({ + title: newPost.title, + body: newPost.body, + userId: newPost.userId + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }) + .then((response) => { + if (!response.ok) throw new Error("Errore nell'invio del post"); + return response.json(); + }) + .then((json) => { + dispatch({type:'ADD_POST', payload:json}) + console.log("Post aggiunto:", json); + }) + .catch((error) => console.error("Errore:", error)); + }, + UpdatePost: (id: number, updateFields: Partial) => { + const postUpdate = posts.find((post:PostType) => post.id === id) + + if (!postUpdate) { + console.error(`Post non trovato per l'id:`, id); + return + } + + + fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { + method: "PUT", + body: JSON.stringify({ + ...postUpdate, + ...updateFields, + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }) + .then((response) => { + if (!response.ok) throw new Error("Errore nell'aggiornamento del post"); + return response.json(); + }) + .then((json) => { + dispatch({type:'UPDATE_POST', payload:{id,updatedFields:json}}) + window.confirm("Post aggiornato"); + }) + .catch((error) => console.error("Errore:", error)); + }, + DeletePost: (id: number) => { + if (!window.confirm("Sei sicuro di voler eliminare questo post?")) return; + + fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { + method: "DELETE", + }) + .then((response) => { + if (!response.ok) throw new Error("Errore nell'eliminazione del post"); + window.confirm(`Post con ID ${id} eliminato con successo.`); + dispatch({type:'DELETE_POST', payload:id}) + }) + .catch((error) => + console.error("Errore durante l'eliminazione del post:", error) + ); + } + } + + + return { + posts, + postFunctions + } +} +export default usePostData + + + diff --git a/tutorial-react-ts/src/blog/reducer/postReducer.ts b/tutorial-react-ts/src/blog/reducer/postReducer.ts new file mode 100644 index 0000000..dac7ac3 --- /dev/null +++ b/tutorial-react-ts/src/blog/reducer/postReducer.ts @@ -0,0 +1,23 @@ +import { PostActionType } from "../definitions/PostActionType"; +import { PostType } from "../definitions/PostType"; + + +export function postReducer(state: PostType[], action: PostActionType) { + switch (action.type) { + case 'SET_POSTS': + return action.payload + case 'ADD_POST': + return [...state, action.payload] + + case "UPDATE_POST": + return state.map((post) => + post.id === action.payload.id ? + { ...post, ...action.payload.updatedFields } + : post + ) + case "DELETE_POST": + return state.filter((post) => post.id !== action.payload) + default: + return state; + } +} \ No newline at end of file diff --git a/tutorial-react-ts/src/blog/ui/PostItem.tsx b/tutorial-react-ts/src/blog/ui/PostItem.tsx new file mode 100644 index 0000000..630ca78 --- /dev/null +++ b/tutorial-react-ts/src/blog/ui/PostItem.tsx @@ -0,0 +1,16 @@ +import { PostItemProps } from "../definitions/PostItemProps"; + +export function PostItem(props: PostItemProps) { + + const {post, handleUpdate, handleDelete} = props; + + return ( +
+

{post.title}

+
{post.body}
+ + +
+ ); +} +export default PostItem;