mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
fixed ref forwarding on textareas
This commit is contained in:
parent
2b7b756212
commit
140e36b6b7
5 changed files with 58 additions and 44 deletions
|
@ -25,7 +25,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentForm: FC<IProps> = ({ comment, nodeId, saveComment, onCancelEdit }) => {
|
const CommentForm: FC<IProps> = ({ comment, nodeId, saveComment, onCancelEdit }) => {
|
||||||
const [textarea, setTextarea] = useState<HTMLTextAreaElement>();
|
const [textarea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
||||||
const uploader = useFileUploader(
|
const uploader = useFileUploader(
|
||||||
UPLOAD_SUBJECTS.COMMENT,
|
UPLOAD_SUBJECTS.COMMENT,
|
||||||
UPLOAD_TARGETS.COMMENTS,
|
UPLOAD_TARGETS.COMMENTS,
|
||||||
|
@ -55,7 +55,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, saveComment, onCancelEdit })
|
||||||
}, [formik]);
|
}, [formik]);
|
||||||
|
|
||||||
const error = formik.status || formik.errors.text;
|
const error = formik.status || formik.errors.text;
|
||||||
useInputPasteUpload(textarea, uploader.uploadFiles);
|
const onPaste = useInputPasteUpload(uploader.uploadFiles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UploadDropzone onUpload={uploader.uploadFiles}>
|
<UploadDropzone onUpload={uploader.uploadFiles}>
|
||||||
|
@ -63,7 +63,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, saveComment, onCancelEdit })
|
||||||
<FormikProvider value={formik}>
|
<FormikProvider value={formik}>
|
||||||
<FileUploaderProvider value={uploader}>
|
<FileUploaderProvider value={uploader}>
|
||||||
<div className={styles.input}>
|
<div className={styles.input}>
|
||||||
<LocalCommentFormTextarea setRef={setTextarea} />
|
<LocalCommentFormTextarea onPaste={onPaste} ref={setTextArea} />
|
||||||
|
|
||||||
{!!error && (
|
{!!error && (
|
||||||
<div className={styles.error} onClick={clearError}>
|
<div className={styles.error} onClick={clearError}>
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import React, { FC, KeyboardEventHandler, useCallback } from 'react';
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
KeyboardEventHandler,
|
||||||
|
TextareaHTMLAttributes,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
import { Textarea } from '~/components/input/Textarea';
|
import { Textarea } from '~/components/input/Textarea';
|
||||||
import { useCommentFormContext } from '~/hooks/comments/useCommentFormFormik';
|
import { useCommentFormContext } from '~/hooks/comments/useCommentFormFormik';
|
||||||
import { useRandomPhrase } from '~/constants/phrases';
|
import { useRandomPhrase } from '~/constants/phrases';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
setRef?: (r: HTMLTextAreaElement) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalCommentFormTextarea: FC<IProps> = ({ setRef }) => {
|
const LocalCommentFormTextarea = forwardRef<HTMLTextAreaElement, IProps>(({ ...rest }, ref) => {
|
||||||
const { values, handleChange, handleSubmit, isSubmitting } = useCommentFormContext();
|
const { values, handleChange, handleSubmit, isSubmitting } = useCommentFormContext();
|
||||||
|
|
||||||
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||||
|
@ -22,14 +26,15 @@ const LocalCommentFormTextarea: FC<IProps> = ({ setRef }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Textarea
|
<Textarea
|
||||||
|
{...rest}
|
||||||
|
ref={ref}
|
||||||
value={values.text}
|
value={values.text}
|
||||||
handler={handleChange('text')}
|
handler={handleChange('text')}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
ref={setRef}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export { LocalCommentFormTextarea };
|
export { LocalCommentFormTextarea };
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import React, {
|
import React, {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
DetailedHTMLProps,
|
DetailedHTMLProps,
|
||||||
memo,
|
forwardRef,
|
||||||
TextareaHTMLAttributes,
|
TextareaHTMLAttributes,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -13,6 +12,7 @@ import autosize from 'autosize';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
import { InputWrapper } from '~/components/input/InputWrapper';
|
import { InputWrapper } from '~/components/input/InputWrapper';
|
||||||
|
import { useForwardRef } from '~/hooks/dom/useForwardRef';
|
||||||
|
|
||||||
type IProps = DetailedHTMLProps<
|
type IProps = DetailedHTMLProps<
|
||||||
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
|
@ -27,20 +27,23 @@ type IProps = DetailedHTMLProps<
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Textarea = memo<IProps>(
|
const Textarea = forwardRef<HTMLTextAreaElement, IProps>(
|
||||||
({
|
(
|
||||||
placeholder,
|
{
|
||||||
error,
|
placeholder,
|
||||||
minRows = 3,
|
error,
|
||||||
maxRows = 30,
|
minRows = 3,
|
||||||
className,
|
maxRows = 30,
|
||||||
handler,
|
className,
|
||||||
title = '',
|
handler,
|
||||||
value,
|
title = '',
|
||||||
...props
|
value,
|
||||||
}) => {
|
...props
|
||||||
|
},
|
||||||
|
forwardRef
|
||||||
|
) => {
|
||||||
|
const ref = useForwardRef(forwardRef);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
const ref = useRef<HTMLTextAreaElement>(null);
|
|
||||||
|
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
||||||
|
@ -52,20 +55,21 @@ const Textarea = memo<IProps>(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref?.current) return;
|
if (!ref?.current) return;
|
||||||
autosize(ref.current);
|
autosize(ref?.current);
|
||||||
return () => autosize.destroy(ref.current);
|
return () => autosize.destroy(ref);
|
||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current) return;
|
if (!ref?.current) return;
|
||||||
|
|
||||||
autosize.update(ref.current);
|
autosize.update(ref);
|
||||||
}, [value]);
|
}, [ref, value, forwardRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputWrapper title={title} error={error} focused={focused} notEmpty={!!value}>
|
<InputWrapper title={title} error={error} focused={focused} notEmpty={!!value}>
|
||||||
<textarea
|
<textarea
|
||||||
{...props}
|
{...props}
|
||||||
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
...props.style,
|
...props.style,
|
||||||
minHeight: minRows * 20,
|
minHeight: minRows * 20,
|
||||||
|
@ -74,7 +78,6 @@ const Textarea = memo<IProps>(
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={classNames(styles.textarea, className)}
|
className={classNames(styles.textarea, className)}
|
||||||
onChange={onInput}
|
onChange={onInput}
|
||||||
ref={ref}
|
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
|
|
17
src/hooks/dom/useForwardRef.ts
Normal file
17
src/hooks/dom/useForwardRef.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ForwardedRef, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const useForwardRef = <T>(ref: ForwardedRef<T | null>) => {
|
||||||
|
const targetRef = useRef<T | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref) return;
|
||||||
|
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
(ref as any)(targetRef.current);
|
||||||
|
} else {
|
||||||
|
(ref as any).current = targetRef.current;
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return targetRef;
|
||||||
|
};
|
|
@ -1,12 +1,9 @@
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { getImageFromPaste } from '~/utils/uploader';
|
import { getImageFromPaste } from '~/utils/uploader';
|
||||||
|
|
||||||
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
|
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
|
||||||
export const useInputPasteUpload = (
|
export const useInputPasteUpload = (onUpload: (files: File[]) => void) => {
|
||||||
input: HTMLTextAreaElement | HTMLInputElement | undefined,
|
return useCallback(
|
||||||
onUpload: (files: File[]) => void
|
|
||||||
) => {
|
|
||||||
const onPaste = useCallback(
|
|
||||||
async event => {
|
async event => {
|
||||||
const image = await getImageFromPaste(event);
|
const image = await getImageFromPaste(event);
|
||||||
|
|
||||||
|
@ -16,12 +13,4 @@ export const useInputPasteUpload = (
|
||||||
},
|
},
|
||||||
[onUpload]
|
[onUpload]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!input) return;
|
|
||||||
|
|
||||||
input.addEventListener('paste', onPaste);
|
|
||||||
|
|
||||||
return () => input.removeEventListener('paste', onPaste);
|
|
||||||
}, [input, onPaste]);
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue