refactor(link): simplify LinkEditForm state management and improve slug handling

- Removed unnecessary useEffect for slug initialization and replaced it with a more efficient state setup.
- Updated handleSubmit to conditionally include the slug in the payload based on link creation or modification.
- Enhanced form handling by utilizing watch for dynamic slug updates and improved validation messages for slug input.
This commit is contained in:
crbon 2026-01-22 17:27:46 +10:00
parent fc78c4a5ff
commit c3332552d7
2 changed files with 43 additions and 37 deletions

View file

@ -10,7 +10,7 @@ import {
Row,
TextField,
} from '@umami/react-zen';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useConfig, useLinkQuery, useMessages } from '@/components/hooks';
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
import { ChevronDown, ChevronRight, RefreshCw } from '@/components/icons';
@ -42,11 +42,15 @@ export function LinkEditForm({
const { linksUrl } = useConfig();
const hostUrl = linksUrl || LINKS_URL;
const { data, isLoading } = useLinkQuery(linkId);
const [slug, setSlug] = useState(generateId());
const [initialSlug] = useState(() => generateId());
const [showAdvanced, setShowAdvanced] = useState(false);
const handleSubmit = async (data: any) => {
await mutateAsync(data, {
const handleSubmit = async (formData: any) => {
const { slug: formSlug, ...rest } = formData;
// Only include slug if creating new link or if it was modified
const payload = !linkId || formSlug !== data?.slug ? formData : rest;
await mutateAsync(payload, {
onSuccess: async () => {
toast(formatMessage(messages.saved));
touch('links');
@ -56,13 +60,7 @@ export function LinkEditForm({
});
};
const handleSlug = () => {
const slug = generateId();
setSlug(slug);
return slug;
};
const handleSlug = () => generateId();
const checkUrl = (url: string) => {
if (!isValidUrl(url)) {
@ -71,12 +69,6 @@ export function LinkEditForm({
return true;
};
useEffect(() => {
if (data) {
setSlug(data.slug);
}
}, [data]);
if (linkId && isLoading) {
return <Loading placement="absolute" />;
}
@ -85,15 +77,27 @@ export function LinkEditForm({
<Form
onSubmit={handleSubmit}
error={getErrorMessage(error)}
defaultValues={{
slug,
{...(linkId
? {
values: {
...data,
ogTitle: data?.ogTitle || '',
ogDescription: data?.ogDescription || '',
ogImageUrl: data?.ogImageUrl || '',
}}
},
}
: {
defaultValues: {
slug: initialSlug,
ogTitle: '',
ogDescription: '',
ogImageUrl: '',
},
})}
>
{({ setValue }) => {
{({ setValue, watch }) => {
const currentSlug = watch('slug') ?? initialSlug;
return (
<>
<FormField
@ -116,7 +120,7 @@ export function LinkEditForm({
<Label>{formatMessage(labels.link)}</Label>
<Row alignItems="center" gap>
<TextField
value={`${hostUrl}/${slug}`}
value={`${hostUrl}/${currentSlug}`}
autoComplete="off"
isReadOnly
allowCopy
@ -157,17 +161,17 @@ export function LinkEditForm({
<TextField autoComplete="off" />
</FormField>
<Column>
<Label>{formatMessage(labels.path)}</Label>
<TextField
value={slug}
onChange={(value: string) => {
setSlug(value);
setValue('slug', value, { shouldDirty: true });
<FormField
label={formatMessage(labels.path)}
name="slug"
rules={{
required: formatMessage(labels.required),
minLength: { value: 4, message: formatMessage(labels.tooShort) },
maxLength: { value: 100, message: formatMessage(labels.tooLong) },
}}
autoComplete="off"
/>
</Column>
>
<TextField autoComplete="off" minLength={4} maxLength={100} />
</FormField>
</Column>
)}

View file

@ -4,6 +4,8 @@ export const labels = defineMessages({
ok: { id: 'label.ok', defaultMessage: 'OK' },
unknown: { id: 'label.unknown', defaultMessage: 'Unknown' },
required: { id: 'label.required', defaultMessage: 'Required' },
tooShort: { id: 'label.too-short', defaultMessage: 'Too short' },
tooLong: { id: 'label.too-long', defaultMessage: 'Too long' },
save: { id: 'label.save', defaultMessage: 'Save' },
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
continue: { id: 'label.continue', defaultMessage: 'Continue' },