toss / suspensive

Manage asynchronous operations, timing, error handling and detecting intersection of elements easily (one of TanStack Query's community resources)
https://suspensive.org
MIT License
528 stars 51 forks source link

[Feature]: `<Mutation/>` is good? #1091

Closed manudeli closed 3 months ago

manudeli commented 3 months ago

Package Scope

@suspensive/react-query

Description

We should make depth to use useMutation.

const ExamplePage = () => {
  const posts = usePosts();

  return posts.map(post => <PostToUseMutation key={post.id} post={post} />);
};

// PostToUseMutation (This unnecessary name  should be made to use only useMutation)
const PostToUseMutation = ({ post }: { post: Post }) => { // props should be drilled to use useMutation
  const postMutation = useMutation({
    mutationFn: ({ content }: { content: string }) => api.editPost({ postId: post.id}),
  });

  return (
    <>
      <div>{post.content}</div>
      <textarea onChange={e => postMutation.mutateAsync({ content: e.target.value })} />
      {post.comments.map(comment => (
        <CommentToUseMutation key={comment.id} post={post} comment={comment} isPostLoading={postMutation.isLoading} />
      ))}
    </>
  );
};

// CommentToUseMutation (This unnecessary name  should be made to use only useMutation)
const CommentToUseMutation = ({ post, comment, isPostLoading }: { post: Post, comment: Comment, isPostLoading: boolean }) => { // props should be drilled to use useMutation
  const commentMutation = useMutation({
    mutationFn: ({ content }: { content: string }) => api.editComment({ postId: post.id, commentId: comment.id, comment }),
  });

  return (
    <div>
      {isPostLoading ? <Spinner/> : null}
      {comment.content}
      <textarea onChange={e => commentMutation.mutateAsync({ content: e.target.value })} />
    </div>
  );
};

Possible Solution

const ExamplePage = () => {
  const posts = usePosts();

  return posts.map(post => (
    <Mutation 
      key={post.id}
      mutationFn={({ content }: { content: string }) => api.editPost({ postId: post.id, content })}
    >
      {postMutation => (
        <>
          <div>{post.content}</div>
          {post.comments.map(comment => (
            <Mutation
              key={comment.id}
              mutationFn={({ content }: { content: string }) => api.editComment({ postId: post.id, commentId: comment.id, content })}
            >
              {commentMutation => (
                <div>
                  {postMutation.isLoading ? <Spinner/> : null}
                  {comment.content}
                  <textarea onChange={e => commentMutation.mutateAsync({ content: e.target.value })} />
                </div>
              )}
            </Mutation>
          ))}
        </>
      )}
    </Mutation>
  ));
};

No response

etc.

No response

gwansikk commented 3 months ago

I think it's a good attempt! It can reduce unnecessary files and manage them cohesively at the same depth, which will greatly impact productivity.

sungh0lim commented 3 months ago

When I look at this example("Possible Solution"), it is inconvenient to be able to access posts from nested Mutation. Make it with each component will reduce side effects.

I was wondering you are inconvenient to make depth.

manudeli commented 3 months ago

it is inconvenient to be able to access posts from nested Mutation. Make it with each component will reduce side effects.

What side effect is? More info is required to explain side effects

I was wondering you are inconvenient to make depth.

Yes I feel inconveniency to make depth because depth will casue to make unnecessary duplicated type definition (props), and unnecessary component name, inflexibility to change structure of components combination. It's same reason with <SuspenseQuery/> https://suspensive.org/docs/react-query/SuspenseQuery

In my opinion, In example We should focus ExamplePage component than CommentToUseMutation, PostToUseMutation by this Mutation component

sungh0lim commented 3 months ago

When make component like PostToUseMutation and CommentToUseMutation. It is not possible to modify posts inside either component.

When callbacks such as onError, onSettled, and onSuccess are added, I do not want to be able to modify the data of the parent component.

I think the example code should be changed like this.

const ExamplePage = () => {
  const posts = usePosts();

  return posts.map(post => <PostToUseMutation key={post.id} post={post} />);
};

// PostToUseMutation (This unnecessary name  should be made to use only useMutation)
const PostToUseMutation = ({ post }: { post: Post }) => { // props should be drilled to use useMutation
  const postMutation = useMutation({
    mutationFn: ({ content }: { content: string }) => api.editPost({ postId: post.id }),
  });

  if (postMutation.isLoading) {
    return <Spinner/>;
  }

  return (
    <>
      <div>{post.content}</div>
      <textarea onChange={e => postMutation.mutateAsync({ content: e.target.value })} />
      {post.comments.map(comment => (
        <CommentToUseMutation key={comment.id} post={post} comment={comment} />
      ))}
    </>
  );
};

// CommentToUseMutation (This unnecessary name  should be made to use only useMutation)
const CommentToUseMutation = ({ post, comment }: { post: Post, comment: Comment }) => { // props should be drilled to use useMutation
  const commentMutation = useMutation({
    mutationFn: ({ content }: { content: string }) => api.editComment({ postId: post.id, commentId: comment.id, comment }),
  });

  return (
    <div>
      {comment.content}
      <textarea onChange={e => commentMutation.mutateAsync({ content: e.target.value })} />
    </div>
  );
};

After writing the example code, I can understand the advantages of the <Mutation> component. Rather than thinking of it as a comment about the <Mutation> component, think of it as a comment about the example code.