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.