Last active
August 2, 2021 01:17
-
-
Save franciscohanna92/0c4e31551eed5e1b6d688604184e95b6 to your computer and use it in GitHub Desktop.
An idea on how to organize a Model module in React (using react-query as the caching/store service)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { useForm } from "react-hook-form"; | |
import { Post, usePostModel } from "../models/post.model"; | |
interface Props { | |
post?: Post; | |
onSubmit?: () => void; | |
} | |
interface PostForm { | |
title: string; | |
body: string; | |
} | |
const PostForm = ({ post, ...props }: Props) => { | |
const postModel = usePostModel(); | |
const { handleSubmit, register } = useForm<PostForm>({ | |
defaultValues: { | |
title: post?.title, | |
body: post?.body, | |
}, | |
}); | |
const onSubmit = (postForm: PostForm) => { | |
postModel.createPost({ userId: 1, ...postForm }, { onSuccess, onError }); | |
}; | |
const onSuccess = (post?: Post) => { | |
alert("success"); | |
props.onSubmit?.(); | |
}; | |
const onError = (error: Error) => { | |
alert("error"); | |
props.onSubmit?.(); | |
}; | |
const disableForm = postModel.isMutatingPost; | |
return ( | |
<form onSubmit={handleSubmit(onSubmit)}> | |
<label> | |
<b>Title</b> <br /> | |
<input | |
disabled={disableForm} | |
type="text" | |
id="title" | |
{...register("title", { required: true })} | |
/> | |
</label> | |
<br /> | |
<label> | |
<b>Body</b> <br /> | |
<textarea | |
disabled={disableForm} | |
id="body" | |
rows={10} | |
cols={50} | |
{...register("body", { required: true })} | |
></textarea> | |
</label> | |
<br /> | |
<button disabled={disableForm} type="submit"> | |
{post ? "Update" : "Create"} post | |
</button> | |
</form> | |
); | |
}; | |
export default PostForm; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState } from "react"; | |
import { useMutation, useQuery } from "react-query"; | |
import _pickBy from "lodash/pickBy"; | |
import _isUndefined from "lodash/isUndefined"; | |
import _isNull from "lodash/isNull"; | |
export interface Post { | |
userId?: number; | |
id?: number; | |
title: string; | |
body: string; | |
} | |
enum AllPostsQueryParam { | |
userId = "userId", | |
} | |
type AllPostsQueryFilters = Record<AllPostsQueryParam, string>; | |
interface MutatePostSideEffects { | |
onSuccess: (post: Post) => any; | |
onError: (error: Error) => any; | |
} | |
export const usePostModel = () => { | |
const [allPostsQueryFilters, setAllPostsQueryFilters] = useState<string>(); | |
const [currentPostId, setCurrentPostId] = useState<number>(); | |
const allPostsQuery = useQuery<Post[]>( | |
["posts", allPostsQueryFilters], | |
async () => { | |
const res = await fetch( | |
`https://jsonplaceholder.typicode.com/posts?${allPostsQueryFilters}` | |
); | |
return res.json(); | |
}, | |
{ enabled: !!allPostsQueryFilters } | |
); | |
const postByIdQuery = useQuery<Post>( | |
["posts", currentPostId], | |
async () => { | |
const res = await fetch( | |
`https://jsonplaceholder.typicode.com/posts/${currentPostId}` | |
); | |
return res.json(); | |
}, | |
{ enabled: !!currentPostId } | |
); | |
const createPostMutation = useMutation(async (post: Post) => { | |
const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, { | |
method: "POST", | |
body: JSON.stringify(post), | |
}); | |
return res.json(); | |
}); | |
const updatePostMutation = useMutation( | |
async ({ postId, post }: { postId: number; post: Post }) => { | |
const res = await fetch( | |
`https://jsonplaceholder.typicode.com/posts/${postId}`, | |
{ method: "PATCH", body: JSON.stringify(post) } | |
); | |
return res.json(); | |
} | |
); | |
const deletePostMutation = useMutation(async (postId: number) => { | |
const res = await fetch( | |
`https://jsonplaceholder.typicode.com/posts/${postId}`, | |
{ method: "DELETE" } | |
); | |
return res.json(); | |
}); | |
const getAllPosts = (filters?: AllPostsQueryFilters) => { | |
const definedFilters = _pickBy( | |
filters, | |
(filter) => !_isUndefined(filter) && !_isNull(filter) | |
); | |
if (definedFilters) { | |
const queryString = new URLSearchParams(definedFilters).toString(); | |
if (queryString !== allPostsQueryFilters) { | |
setAllPostsQueryFilters(queryString); | |
} | |
} | |
return allPostsQuery; | |
}; | |
const getPostById = (postId: number) => { | |
if (postId && postId !== currentPostId) { | |
setCurrentPostId(postId); | |
} | |
return postByIdQuery; | |
}; | |
const createPost = async ( | |
post: Post, | |
{ onSuccess, onError }: MutatePostSideEffects | |
) => { | |
try { | |
const newPost = await createPostMutation.mutateAsync(post); | |
onSuccess(newPost); | |
} catch (error) { | |
console.error(error); | |
onError(error); | |
} | |
}; | |
const updatePost = async ( | |
postId: number, | |
post: Post, | |
{ onSuccess, onError }: MutatePostSideEffects | |
) => { | |
try { | |
const updatedPost = await updatePostMutation.mutateAsync({ | |
postId, | |
post, | |
}); | |
onSuccess(updatedPost); | |
} catch (error) { | |
console.error(error); | |
onError(error); | |
} | |
}; | |
const deletePost = async ( | |
postId: number, | |
{ onSuccess, onError }: MutatePostSideEffects | |
) => { | |
try { | |
const updatedPost = await deletePostMutation.mutateAsync(postId); | |
onSuccess(updatedPost); | |
} catch (error) { | |
console.error(error); | |
onError(error); | |
} | |
}; | |
return { | |
getAllPosts, | |
getPostById, | |
createPost, | |
updatePost, | |
deletePost, | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Link from "next/link"; | |
import { useRouter } from "next/router"; | |
import React from "react"; | |
import { usePostModel } from "../../../../models/post.model"; | |
interface Props {} | |
const PostsPage = (props: Props) => { | |
const router = useRouter(); | |
const userId = router.query.userId as string; | |
const postModel = usePostModel(); | |
const { data: posts, isLoading } = postModel.getAllPosts({ userId }); | |
if (isLoading || !posts) { | |
return "Loading..."; | |
} | |
return ( | |
<ul> | |
{posts.map((post) => ( | |
<li key={post.id}> | |
<Link href={`/users/${userId}/posts/${post.id}`}> | |
<a>{post.title}</a> | |
</Link> | |
</li> | |
))} | |
</ul> | |
); | |
}; | |
export default PostsPage; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment