Skip to content

Instantly share code, notes, and snippets.

@franciscohanna92
Last active August 2, 2021 01:17
Show Gist options
  • Save franciscohanna92/0c4e31551eed5e1b6d688604184e95b6 to your computer and use it in GitHub Desktop.
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)
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;
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,
};
};
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