Fix navigation memory after custom object rename (#16918)

This PR fixes an issue where exiting Settings after renaming a custom
object could redirect to a stale object URL and result in a `404`. When
a custom object name was updated, the memorized navigation URL was not
kept in sync, causing redirects to use the old object route.
The navigation state is now updated only when the memorized URL belongs
to the renamed object, ensuring redirects always point to the correct
route while preserving unrelated navigation context.

Fixes https://github.com/twentyhq/twenty/issues/11291

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
Rajdeep Das
2026-01-05 23:16:22 +05:30
committed by GitHub
parent e6908e4635
commit de33d17809
2 changed files with 47 additions and 0 deletions
@@ -1,15 +1,18 @@
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange } from '@/settings/data-model/object-details/utils/computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange.util';
import { SettingsDataModelObjectAboutForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import {
type SettingsDataModelObjectAboutFormValues,
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { zodResolver } from '@hookform/resolvers/zod';
import { FormProvider, useForm } from 'react-hook-form';
import { useSetRecoilState } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
@@ -27,6 +30,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
const setUpdatedObjectNamePlural = useSetRecoilState(
updatedObjectNamePluralState,
);
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
const {
description,
@@ -95,6 +102,21 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
navigate(SettingsPath.ObjectDetail, {
objectNamePlural: objectNamePluralForRedirection,
});
const updatedObjectNamePlural =
updatedObject?.data?.updateOneObject.namePlural;
if (!isDefined(updatedObjectNamePlural)) {
return;
}
setNavigationMemorizedUrl((previousNavigationMemorizedUrl) =>
computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange(
previousNavigationMemorizedUrl,
objectMetadataItem.namePlural,
updatedObjectNamePlural,
),
);
};
const updateObjectMetadata = async (
@@ -0,0 +1,25 @@
export const computeUpdatedNavigationMemorizedUrlAfterObjectNamePluralChange = (
navigationMemorizedUrl: string,
previousObjectNamePlural: string,
updatedObjectNamePlural: string,
): string => {
const objectRoutePatternWithPreviousNamePlural = `^/objects/${previousObjectNamePlural}`;
const objectRouteRegexMatchingPreviousNamePlural = new RegExp(
`${objectRoutePatternWithPreviousNamePlural}(/|\\?|$)`,
);
const isNavigationMemorizedUrlMatchingPreviousObjectRoute =
objectRouteRegexMatchingPreviousNamePlural.test(navigationMemorizedUrl);
if (!isNavigationMemorizedUrlMatchingPreviousObjectRoute) {
return navigationMemorizedUrl;
}
const navigationMemorizedUrlWithUpdatedObjectNamePlural =
navigationMemorizedUrl.replace(
new RegExp(objectRoutePatternWithPreviousNamePlural),
`/objects/${updatedObjectNamePlural}`,
);
return navigationMemorizedUrlWithUpdatedObjectNamePlural;
};