Skip to content

Commit

Permalink
fix(CommentForm): remove render props, pass values from parent
Browse files Browse the repository at this point in the history
  • Loading branch information
awinogradov committed Aug 3, 2023
1 parent 0c9fd94 commit 655fdee
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 56 deletions.
38 changes: 34 additions & 4 deletions src/components/CommentCreateForm/CommentCreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,45 @@ const CommentCreateForm: React.FC<CommentCreateFormProps> = ({ goalId, states, o
const { user, themeId } = usePageContext();
const { create } = useCommentResource();
const [pushState, setPushState] = useState<State | undefined>();
const [description, setDescription] = useState<string>();
const [focused, setFocused] = useState(false);
const [busy, setBusy] = useState(false);
const [currentGoal, setCurrentGoal] = useState('');
const [prevGoal, setPrevGoal] = useState('');

if (goalId !== prevGoal) {
setCurrentGoal(goalId);
setPrevGoal(goalId);
setDescription('');
setFocused(false);
}

const onCommentFocus = useCallback(() => {
setFocused(true);
onFocus?.();
}, [onFocus]);

const createComment = useCallback(
async (form: GoalCommentSchema) => {
setBusy(true);
setFocused(false);

await create(({ id }) => {
onSubmit?.(id);
setBusy(false);
setFocused(true);
setDescription('');
setPushState(undefined);
onSubmit?.(id);
})(form);
},
[create, onSubmit],
);

const onCancelCreate = useCallback(() => {
setBusy(false);
setFocused(false);
setPushState(undefined);
setDescription('');
onCancel?.();
}, [onCancel]);

Expand All @@ -59,12 +85,16 @@ const CommentCreateForm: React.FC<CommentCreateFormProps> = ({ goalId, states, o
<UserPic size={32} src={user?.image} email={user?.email} />

<CommentForm
goalId={goalId}
goalId={currentGoal}
stateId={pushState?.id}
description={description}
focused={focused}
busy={busy}
onDescriptionChange={setDescription}
onSubmit={createComment}
onCancel={onCancelCreate}
onFocus={onFocus}
renderActionButton={({ busy }) =>
onFocus={onCommentFocus}
actionButton={
states ? (
<StyledStateUpdate>
<Button
Expand Down
43 changes: 38 additions & 5 deletions src/components/CommentEditForm/CommentEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { Button } from '@taskany/bricks';

import { useCommentResource } from '../../hooks/useCommentResource';
Expand All @@ -15,16 +15,38 @@ interface CommentEditFormProps {
onUpdate: (comment?: { id: string; description: string }) => void;
onChange: (comment: { description: string }) => void;
onCancel: () => void;
onFocus?: () => void;
}

const CommentEditForm: React.FC<CommentEditFormProps> = ({ id, description, onUpdate, onChange, onCancel }) => {
const CommentEditForm: React.FC<CommentEditFormProps> = ({
id,
description: currentDescription,
onUpdate,
onChange,
onCancel,
onFocus,
}) => {
const { update } = useCommentResource();
const [description, setDescription] = useState(currentDescription);
const [focused, setFocused] = useState(false);
const [busy, setBusy] = useState(false);

const onCommentFocus = useCallback(() => {
setFocused(true);
onFocus?.();
}, [onFocus]);

const onCommentUpdate = useCallback(
(form: GoalCommentSchema) => {
setBusy(true);
setFocused(false);

// optimistic update
onChange?.({ description: form.description });

update((comment) => {
setBusy(false);
setDescription(comment.description);
onUpdate?.(comment);
})(form);
},
Expand All @@ -35,13 +57,24 @@ const CommentEditForm: React.FC<CommentEditFormProps> = ({ id, description, onUp
<CommentForm
id={id}
description={description}
focused={focused}
busy={busy}
autoFocus
height={120}
onDescriptionChange={setDescription}
onFocus={onCommentFocus}
onSubmit={onCommentUpdate}
onCancel={onCancel}
renderActionButton={({ busy }) => (
<Button size="m" view="primary" disabled={busy} outline type="submit" text={tr('Save')} />
)}
actionButton={
<Button
size="m"
view="primary"
disabled={currentDescription === description || busy}
outline
type="submit"
text={tr('Save')}
/>
}
/>
);
};
Expand Down
76 changes: 29 additions & 47 deletions src/components/CommentForm/CommentForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
Expand All @@ -12,16 +12,19 @@ import { HelpButton } from '../HelpButton/HelpButton';
import { tr } from './CommentForm.i18n';

interface CommentFormProps {
actionButton: React.ReactNode;
focused?: boolean;
autoFocus?: boolean;
busy?: boolean;
height?: number;
error?: React.ComponentProps<typeof FormEditor>['error'];
id?: string;
goalId?: string;
stateId?: string;
description?: string;

renderActionButton: (props: { busy?: boolean }) => React.ReactNode;
onSubmit?: (form: GoalCommentSchema) => void | Promise<void>;
onDescriptionChange: (description: string) => void;
onSubmit: (form: GoalCommentSchema) => void | Promise<void>;
onFocus?: () => void;
onCancel?: () => void;
}
Expand Down Expand Up @@ -62,76 +65,55 @@ export const CommentForm: React.FC<CommentFormProps> = ({
stateId,
description = '',
autoFocus,
focused,
busy,
error,
renderActionButton,
actionButton,
onDescriptionChange,
onSubmit,
onFocus,
onCancel,
}) => {
const [commentFocused, setCommentFocused] = useState(false);
const [busy, setBusy] = useState(false);
const ref = useRef(null);

const { control, handleSubmit, reset, register } = useForm<GoalCommentSchema>({
const { control, handleSubmit, register, watch } = useForm<GoalCommentSchema>({
resolver: zodResolver(goalCommentSchema),
mode: 'onChange',
reValidateMode: 'onChange',
shouldFocusError: true,
values: {
id,
goalId,
stateId,
id,
description,
},
});

const setBusyAndCommentFocused = useCallback((busy: boolean, commentFocused: boolean) => {
setBusy(busy);
setCommentFocused(commentFocused);
}, []);

const onCommentFocus = useCallback(() => {
setCommentFocused(true);
onFocus?.();
}, [onFocus]);

const onCommentCancel = useCallback(() => {
reset();
setBusyAndCommentFocused(false, false);
onCancel?.();
}, [onCancel, reset, setBusyAndCommentFocused]);
const descriptionWatcher = watch('description');

const onCommentSubmit = useCallback(
async (form: GoalCommentSchema) => {
setBusyAndCommentFocused(true, false);
reset();

await onSubmit?.(form);

setBusyAndCommentFocused(false, true);
},
[onSubmit, reset, setBusyAndCommentFocused],
);
useEffect(() => {
onDescriptionChange(descriptionWatcher);
}, [descriptionWatcher, onDescriptionChange]);

useClickOutside(ref, () => {
if (!Object.values(control._fields).some((v) => v?._f.value)) {
onCommentCancel();
onCancel?.();
}
});

return (
<StyledCommentForm ref={ref} tabIndex={0}>
<Form onSubmit={handleSubmit(onCommentSubmit)}>
<Form onSubmit={handleSubmit(onSubmit)}>
{nullable(id, () => (
<input type="hidden" value={id} {...register('id')} />
<input type="hidden" {...register('id')} />
))}

{nullable(goalId, () => (
<input type="hidden" value={goalId} {...register('goalId')} />
<input type="hidden" {...register('goalId')} />
))}

{nullable(stateId, () => (
<input type="hidden" value={stateId} {...register('stateId')} />
<input type="hidden" {...register('stateId')} />
))}

<Controller
Expand All @@ -142,30 +124,30 @@ export const CommentForm: React.FC<CommentFormProps> = ({
{...field}
disabled={busy}
placeholder={tr('Leave a comment')}
height={commentFocused ? 120 : 40}
onCancel={onCommentCancel}
onFocus={onCommentFocus}
height={focused ? 120 : 40}
onCancel={onCancel}
onFocus={onFocus}
autoFocus={autoFocus}
error={commentFocused ? error : undefined}
error={focused ? error : undefined}
/>
)}
/>

{nullable(commentFocused, () => (
{nullable(focused, () => (
<FormActions>
<FormAction left inline>
{nullable(commentFocused, () => (
{nullable(focused, () => (
<StyledFormBottom>
<HelpButton slug="comments" />
</StyledFormBottom>
))}
</FormAction>
<FormAction right inline>
{nullable(!busy, () => (
<Button outline text={tr('Cancel')} onClick={onCommentCancel} />
<Button outline text={tr('Cancel')} onClick={onCancel} />
))}

{renderActionButton({ busy })}
{actionButton}
</FormAction>
</FormActions>
))}
Expand Down

0 comments on commit 655fdee

Please sign in to comment.