Home > Back-end >  How to handle repeated similar types in TypeScript
How to handle repeated similar types in TypeScript

Time:10-07

In the simplified version of my app I am receiving comments from the server and each comment has replies array as a property. I want to normalize my state so that I have comments without replies as one part of the state and replies as a second part of the state.

When I create a comment I only submit author and body. When I receive comment from the server I get additional properties along with replies array and in the state I want to have comments without replies.

Because if that in my types.ts file I have 4 different interfaces. One is for the New Comment, the other one for comment without replies which goes into state, third one is for the whole comment received from the server and fourth one just for the reply.

export interface NewComment {
  author: string,
  body: string
}

export interface CommentWithoutReplies {
  id: string,
  author: string,
  body: string,
  postedAt: number,
  replies_count: number,
}

export interface Comment {
  id: string,
  author: string,
  body: string,
  postedAt: number,
  replies_count: number,
  replies: Reply[]
}

export interface Reply {
  id: string
  comment_id: string,
  author: string,
  body: string,
  postedAt: number,
}

I am using creating a comment-context file which now looks like this and passes the check:

import { createContext, useCallback, useState, FC } from "react";
import {Comment, CommentWithoutReplies, CommentContextState, CommentContextDispatch} from "../types/types"

const defaultStateValue: CommentContextState = {
  comments: [],
};
const defaultDispatchValue: CommentContextDispatch = {
  getComments: () => undefined,
  addComment: () => undefined,
};

export const CommentStateContext = createContext<CommentContextState>(defaultStateValue);
export const CommentDispatchContext = createContext<CommentContextDispatch>(defaultDispatchValue);

const CommentProvider: FC = ({children}) => {
  const [comments, setComments] = useState<CommentWithoutReplies[]>(defaultStateValue.comments);

  const getComments = useCallback(
    (data: Comment[]) => {
      setComments(() => {
        return data.map(comment => {
          const {replies, ...commentWithoutReplies} = comment;
          return commentWithoutReplies
        })
      });
    },
    [setComments]
  );

  const addComment = useCallback(
    (data: CommentWithoutReplies) => {
      setComments((currentComments: CommentWithoutReplies[]) => {
        return [...currentComments, data];
      });
    },
    [setComments]
  );

  return (
    <CommentDispatchContext.Provider
      value={{
        getComments,
        addComment,
      }}
    >
      <CommentStateContext.Provider
        value={{
          comments,
        }}
      >
        {children}
      </CommentStateContext.Provider>
    </CommentDispatchContext.Provider>
  );
};

export default CommentProvider;

Is there a more graceful way to handle these types than to have 4 different interfaces?

CodePudding user response:

What you're doing now looks quite reasonable to me - you're using multiple separate shapes of data, so you need to define those each of those shapes somewhere, in most cases. Though, you can make things less repetitive by not noting the types when not necessary, eg this

setComments((currentComments: CommentWithoutReplies[]) => {

can be

setComments((currentComments) => {

and you can define the types more concisely by starting with a base type that contains all the properties in common - id, author, postedAt.

type BaseComment {
    id: string,
    author: string,
    postedAt: number,
}
export type CommentWithoutReplies = BaseComment & {
    body: string,
    replies_count: number,
}
export type Comment = BaseComment & {
    body: string,
    replies_count: number,
    replies: Reply[]
}
export type Reply = BaseComment & {
    comment_id: string,
    body: string,
}

Don't know what other code you have, but if the types here aren't being imported elsewhere, you could also define them in the context file itself instead of having to import them.

  • Related