From f3727440ff7d25fbd17883bcf667b14487555927 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 29 Mar 2023 20:14:41 -0500 Subject: [PATCH] Move form hooks into their own files --- app/soapbox/features/group/edit-group.tsx | 61 +---------------------- app/soapbox/hooks/forms/index.ts | 3 ++ app/soapbox/hooks/forms/useImageField.ts | 40 +++++++++++++++ app/soapbox/hooks/forms/usePreview.ts | 12 +++++ app/soapbox/hooks/forms/useTextField.ts | 23 +++++++++ 5 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 app/soapbox/hooks/forms/index.ts create mode 100644 app/soapbox/hooks/forms/useImageField.ts create mode 100644 app/soapbox/hooks/forms/usePreview.ts create mode 100644 app/soapbox/hooks/forms/useTextField.ts diff --git a/app/soapbox/features/group/edit-group.tsx b/app/soapbox/features/group/edit-group.tsx index c2a38397b..da2a01666 100644 --- a/app/soapbox/features/group/edit-group.tsx +++ b/app/soapbox/features/group/edit-group.tsx @@ -1,13 +1,13 @@ import clsx from 'clsx'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import Icon from 'soapbox/components/icon'; import { Avatar, Button, Column, Form, FormActions, FormGroup, HStack, Input, Spinner, Text, Textarea } from 'soapbox/components/ui'; import { useAppSelector, useInstance } from 'soapbox/hooks'; import { useGroup, useUpdateGroup } from 'soapbox/hooks/api'; +import { useImageField, useTextField } from 'soapbox/hooks/forms'; import { isDefaultAvatar, isDefaultHeader } from 'soapbox/utils/accounts'; -import resizeImage from 'soapbox/utils/resize-image'; import type { List as ImmutableList } from 'immutable'; @@ -178,61 +178,4 @@ const EditGroup: React.FC = ({ params: { id: groupId } }) => { ); }; -function usePreview(file: File | null | undefined): string | undefined { - return useMemo(() => { - if (file) { - return URL.createObjectURL(file); - } - }, [file]); -} - -function useTextField(initialValue: string | undefined) { - const [value, setValue] = useState(initialValue); - const hasInitialValue = typeof initialValue === 'string'; - - const onChange: React.ChangeEventHandler = (e) => { - setValue(e.target.value); - }; - - useEffect(() => { - if (hasInitialValue) { - setValue(initialValue); - } - }, [hasInitialValue]); - - return { - value, - onChange, - }; -} - -interface UseImageFieldOpts { - maxPixels?: number - preview?: string -} - -function useImageField(opts: UseImageFieldOpts = {}) { - const [file, setFile] = useState(); - const src = usePreview(file) || opts.preview; - - const onChange: React.ChangeEventHandler = ({ target: { files } }) => { - const file = files?.item(0) || undefined; - if (file) { - if (typeof opts.maxPixels === 'number') { - resizeImage(file, opts.maxPixels) - .then((f) => setFile(f)) - .catch(console.error); - } else { - setFile(file); - } - } - }; - - return { - src, - file, - onChange, - }; -} - export default EditGroup; diff --git a/app/soapbox/hooks/forms/index.ts b/app/soapbox/hooks/forms/index.ts new file mode 100644 index 000000000..7e2c9e7e1 --- /dev/null +++ b/app/soapbox/hooks/forms/index.ts @@ -0,0 +1,3 @@ +export { useImageField } from './useImageField'; +export { useTextField } from './useTextField'; +export { usePreview } from './usePreview'; \ No newline at end of file diff --git a/app/soapbox/hooks/forms/useImageField.ts b/app/soapbox/hooks/forms/useImageField.ts new file mode 100644 index 000000000..d719653f1 --- /dev/null +++ b/app/soapbox/hooks/forms/useImageField.ts @@ -0,0 +1,40 @@ +import { useState } from 'react'; + +import resizeImage from 'soapbox/utils/resize-image'; + +import { usePreview } from './usePreview'; + +interface UseImageFieldOpts { + /** Resize the image to the max dimensions, if defined. */ + maxPixels?: number + /** Fallback URL before a file is uploaded. */ + preview?: string +} + +/** Handle image, and optionally resize it. */ +function useImageField(opts: UseImageFieldOpts = {}) { + const [file, setFile] = useState(); + const src = usePreview(file) || opts.preview; + + const onChange: React.ChangeEventHandler = ({ target: { files } }) => { + const file = files?.item(0) || undefined; + if (file) { + if (typeof opts.maxPixels === 'number') { + resizeImage(file, opts.maxPixels) + .then((f) => setFile(f)) + .catch(console.error); + } else { + setFile(file); + } + } + }; + + return { + src, + file, + onChange, + }; +} + +export { useImageField }; +export type { UseImageFieldOpts }; \ No newline at end of file diff --git a/app/soapbox/hooks/forms/usePreview.ts b/app/soapbox/hooks/forms/usePreview.ts new file mode 100644 index 000000000..272ea696e --- /dev/null +++ b/app/soapbox/hooks/forms/usePreview.ts @@ -0,0 +1,12 @@ +import { useMemo } from 'react'; + +/** Return a preview URL for a file. */ +function usePreview(file: File | null | undefined): string | undefined { + return useMemo(() => { + if (file) { + return URL.createObjectURL(file); + } + }, [file]); +} + +export { usePreview }; \ No newline at end of file diff --git a/app/soapbox/hooks/forms/useTextField.ts b/app/soapbox/hooks/forms/useTextField.ts new file mode 100644 index 000000000..7354f58c7 --- /dev/null +++ b/app/soapbox/hooks/forms/useTextField.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +function useTextField(initialValue: string | undefined) { + const [value, setValue] = useState(initialValue); + const hasInitialValue = typeof initialValue === 'string'; + + const onChange: React.ChangeEventHandler = (e) => { + setValue(e.target.value); + }; + + useEffect(() => { + if (hasInitialValue) { + setValue(initialValue); + } + }, [hasInitialValue]); + + return { + value, + onChange, + }; +} + +export { useTextField }; \ No newline at end of file