From 596ab02c28aef8f5de173b160e3c9a9bd3ec1f73 Mon Sep 17 00:00:00 2001 From: DenisVorop Date: Sat, 5 Aug 2023 00:40:05 +0300 Subject: [PATCH] chore: create a draft hook --- .../CommentCreateForm/CommentCreateForm.tsx | 63 +++++++++--------- src/components/GoalActivity.tsx | 33 ++++++---- src/hooks/useLSDraft.ts | 64 +++++++++++++++++++ src/types/draftComment.ts | 3 - 4 files changed, 114 insertions(+), 49 deletions(-) create mode 100644 src/hooks/useLSDraft.ts delete mode 100644 src/types/draftComment.ts diff --git a/src/components/CommentCreateForm/CommentCreateForm.tsx b/src/components/CommentCreateForm/CommentCreateForm.tsx index b20f99b3f..ef4b8ab6c 100644 --- a/src/components/CommentCreateForm/CommentCreateForm.tsx +++ b/src/components/CommentCreateForm/CommentCreateForm.tsx @@ -1,8 +1,7 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { ArrowDownSmallIcon, ArrowUpSmallIcon, Button, Dropdown, UserPic } from '@taskany/bricks'; import { State } from '@prisma/client'; import styled from 'styled-components'; -import { debounce } from 'throttle-debounce'; import { usePageContext } from '../../hooks/usePageContext'; import { useCommentResource } from '../../hooks/useCommentResource'; @@ -11,19 +10,19 @@ import { CommentForm } from '../CommentForm/CommentForm'; import { ActivityFeedItem } from '../ActivityFeed'; import { ColorizedMenuItem } from '../ColorizedMenuItem'; import { StateDot } from '../StateDot'; -import { DraftComment } from '../../types/draftComment'; import { tr } from './CommentCreateForm.i18n'; interface CommentCreateFormProps { goalId: string; states?: State[]; - draftComment?: DraftComment; + description?: string; + stateId?: string; onSubmit?: (id?: string) => void; onFocus?: () => void; onCancel?: () => void; - onDraftComment?: (comment: DraftComment | null) => void; + onChange?: (comment?: { description?: string; stateId?: string }) => void; } const StyledStateUpdate = styled.div` @@ -31,50 +30,52 @@ const StyledStateUpdate = styled.div` align-items: center; `; -const findStateHelper = (draftComment?: DraftComment) => (state?: State) => state?.id === draftComment?.stateId; - const CommentCreateForm: React.FC = ({ goalId, states, - draftComment, + description: currentDescription, + stateId = '', onSubmit, onFocus, onCancel, - onDraftComment, + onChange, }) => { + const statesMap = useMemo(() => { + if (!states) return {}; + + return states.reduce((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {} as Record); + }, [states]); + const { user, themeId } = usePageContext(); const { create } = useCommentResource(); - const [pushState, setPushState] = useState(); - const [description, setDescription] = useState(''); - const [focused, setFocused] = useState(false); + const [pushState, setPushState] = useState(statesMap[stateId]); + const [description, setDescription] = useState(currentDescription); + const [focused, setFocused] = useState(Boolean(currentDescription)); const [busy, setBusy] = useState(false); const [currentGoal, setCurrentGoal] = useState(goalId); const [prevGoal, setPrevGoal] = useState(''); - // eslint-disable-next-line react-hooks/exhaustive-deps - const debouncedOnDraftComment = useCallback( - debounce(500, (comment: DraftComment) => { - onDraftComment?.(comment); - }), - [onDraftComment], - ); - useEffect(() => { - if (!description) return; + if (!description) { + onChange?.(); + return; + } - debouncedOnDraftComment({ description, stateId: pushState?.id }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pushState?.id, description]); + onChange?.({ description, stateId: pushState?.id }); + }, [pushState?.id, description, onChange]); useEffect(() => { if (goalId === prevGoal) return; setCurrentGoal(goalId); setPrevGoal(goalId); - setDescription(draftComment?.description ?? ''); - setFocused(Boolean(draftComment?.description)); - setPushState(states?.find(findStateHelper(draftComment))); - }, [draftComment, goalId, prevGoal, states]); + setDescription(currentDescription); + setFocused(Boolean(currentDescription)); + setPushState(statesMap[stateId]); + }, [currentDescription, goalId, prevGoal, stateId, statesMap]); const onCommentFocus = useCallback(() => { setFocused(true); @@ -90,13 +91,12 @@ const CommentCreateForm: React.FC = ({ onSubmit?.(id); setDescription(''); setPushState(undefined); - onDraftComment?.(null); })(form); setBusy(false); setFocused(true); }, - [create, onSubmit, onDraftComment], + [create, onSubmit], ); const onCancelCreate = useCallback(() => { @@ -105,8 +105,7 @@ const CommentCreateForm: React.FC = ({ setPushState(undefined); setDescription(''); onCancel?.(); - onDraftComment?.(null); - }, [onCancel, onDraftComment]); + }, [onCancel]); const onStateSelect = useCallback((state: State) => { setPushState((prev) => (state.id === prev?.id ? undefined : state)); diff --git a/src/components/GoalActivity.tsx b/src/components/GoalActivity.tsx index 2116ac8ea..5c195304d 100644 --- a/src/components/GoalActivity.tsx +++ b/src/components/GoalActivity.tsx @@ -7,8 +7,7 @@ import { Priority } from '../types/priority'; import { useHighlightedComment } from '../hooks/useHighlightedComment'; import { GoalByIdReturnType } from '../../trpc/inferredTypes'; import { HistoryAction } from '../types/history'; -import { useLocalStorage } from '../hooks/useLocalStorage'; -import { DraftComment } from '../types/draftComment'; +import { useLSDraft } from '../hooks/useLSDraft'; import { ActivityFeed } from './ActivityFeed'; import { CommentView } from './CommentView/CommentView'; @@ -45,23 +44,27 @@ function excludeString(val: T): Exclude { export const GoalActivity = forwardRef( ({ feed, onCommentReaction, onCommentPublish, userId, goalId, goalStates, onCommentDelete, children }, ref) => { - const [draftComment, setDraftComment] = useLocalStorage('draftGoalComment', {}); + const { saveDraft, resolveDraft, removeDraft } = useLSDraft('draftGoalComment', {}); const { highlightCommentId, setHighlightCommentId } = useHighlightedComment(); + const draft = resolveDraft(goalId); + const onPublish = (id?: string) => { onCommentPublish(id); setHighlightCommentId(id); + removeDraft(goalId); + }; + + const onCancel = () => { + removeDraft(goalId); }; - const onDraftComment = (comment: DraftComment | null) => { - setDraftComment((prev) => { - if (comment) { - prev[goalId] = comment; - } else { - delete prev[goalId]; - } - return prev; - }); + const onChange = (comment?: { stateId?: string; description?: string }) => { + if (!comment) { + removeDraft(goalId); + return; + } + saveDraft(goalId, comment); }; return ( @@ -163,9 +166,11 @@ export const GoalActivity = forwardRef( ); diff --git a/src/hooks/useLSDraft.ts b/src/hooks/useLSDraft.ts new file mode 100644 index 000000000..ddc5d73df --- /dev/null +++ b/src/hooks/useLSDraft.ts @@ -0,0 +1,64 @@ +import { useCallback } from 'react'; + +import { DraftGoalComment, useLocalStorage } from './useLocalStorage'; + +type LSParams = Parameters; + +type DraftComment = DraftGoalComment[keyof DraftGoalComment]; +type DraftGoalCommentKey = 'draftGoalComment'; +type DraftGoalCommentKeyReturn = { + saveDraft: (id: string, draft: DraftComment) => void; + resolveDraft: (id: string) => DraftComment; + removeDraft: (id: string) => void; +}; + +function useLSDraft( + storageKey: DraftGoalCommentKey, + initialValue: Record, +): DraftGoalCommentKeyReturn; + +/** + * before using, you need to set up an overload + */ +function useLSDraft( + storageKey: KDG, + initialValue: Record, +): DraftGoalCommentKeyReturn { + const [draft, setDraft] = useLocalStorage(storageKey, initialValue); + + const saveDraft = useCallback( + (id: string, draft: DraftComment) => { + setDraft((prev) => { + // @ts-expect-error + prev[id] = draft; + return prev; + }); + }, + [setDraft], + ); + + const resolveDraft = useCallback( + (id: string) => { + return draft[id]; + }, + [draft], + ); + + const removeDraft = useCallback( + (id: string) => { + setDraft((prev) => { + delete prev[id]; + return prev; + }); + }, + [setDraft], + ); + + return { + saveDraft, + resolveDraft, + removeDraft, + }; +} + +export { useLSDraft }; diff --git a/src/types/draftComment.ts b/src/types/draftComment.ts deleted file mode 100644 index 6f3b227d3..000000000 --- a/src/types/draftComment.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { DraftGoalComment } from '../hooks/useLocalStorage'; - -export type DraftComment = DraftGoalComment[keyof DraftGoalComment];