Improve type safety and remove unnecessary store operations (#18622)

## Summary
This PR improves type safety across the codebase by replacing generic
`any` types with proper TypeScript types, removes unnecessary record
store operations, and adds TODO comments for future refactoring of
useEffect hooks.

## Key Changes

### Type Safety Improvements
- **SettingsAgentTurnDetail.tsx**: Replaced `any` type annotations with
proper `AgentMessage` type from generated GraphQL types
- **useCreateManyRecords.ts**: Added `RecordGqlNode` type for better
type safety when handling mutation responses
- **useLazyFindOneRecord.ts**: Replaced generic `Record<string, any>`
with `Record<string, RecordGqlNode>` for improved type checking

### Removed Unnecessary Operations
- **EventCardCalendarEvent.tsx**: Removed unused
`useUpsertRecordsInStore` hook and its associated useEffect that was
upserting calendar event records to the store
- **EventCardMessage.tsx**: Removed unused `useUpsertRecordsInStore`
hook and its associated useEffect that was upserting message records to
the store

### Conditional Query Execution
- **useLoadCurrentUser.ts**: Made the `FindAllCoreViewsDocument` query
conditional - only executes when `isOnAWorkspace` is true, preventing
unnecessary queries for users not on a workspace

### Documentation
- Added TODO comments in multiple files (`useAgentChatData.ts`,
`useWorkspaceFromInviteHash.ts`, `useGetPublicWorkspaceDataByDomain.ts`,
`useFindManyRecords.ts`, `useSingleRecordPickerPerformSearch.ts`)
referencing PR #18584 for future refactoring of useEffect hooks to avoid
unnecessary re-renders

## Implementation Details
- The removal of store upsert operations suggests these records are
already being managed elsewhere or the operations were redundant
- Type improvements maintain backward compatibility while providing
better IDE support and compile-time checking
- Conditional query execution reduces unnecessary network requests and
improves performance for non-workspace users

https://claude.ai/code/session_01YQErkoHotMvM6VL3JkWAqV

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Félix Malfait
2026-03-13 17:14:56 +01:00
committed by GitHub
parent b470cb21a1
commit 4b6c8d52e5
11 changed files with 37 additions and 35 deletions
@@ -8,9 +8,8 @@ import { type CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { useOpenCalendarEventInSidePanel } from '@/side-panel/hooks/useOpenCalendarEventInSidePanel';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { UserContext } from '@/users/contexts/UserContext';
import { useContext, useEffect } from 'react';
import { useContext } from 'react';
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { isDefined } from 'twenty-shared/utils';
@@ -97,7 +96,6 @@ export const EventCardCalendarEvent = ({
}: {
calendarEventId: string;
}) => {
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { openCalendarEventInSidePanel } = useOpenCalendarEventInSidePanel();
const {
@@ -121,12 +119,6 @@ export const EventCardCalendarEvent = ({
},
});
useEffect(() => {
if (calendarEvent) {
upsertRecordsInStore({ partialRecords: [calendarEvent] });
}
}, [calendarEvent, upsertRecordsInStore]);
const { timeZone } = useContext(UserContext);
if (isDefined(error)) {
@@ -6,9 +6,7 @@ import { EventCardMessageForbidden } from '@/activities/timeline-activities/rows
import { useOpenEmailThreadInSidePanel } from '@/side-panel/hooks/useOpenEmailThreadInSidePanel';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { Trans, useLingui } from '@lingui/react/macro';
import { useEffect } from 'react';
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { isDefined } from 'twenty-shared/utils';
@@ -63,7 +61,6 @@ export const EventCardMessage = ({
authorFullName: string;
}) => {
const { t } = useLingui();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { openEmailThreadInSidePanel } = useOpenEmailThreadInSidePanel();
const {
@@ -85,12 +82,6 @@ export const EventCardMessage = ({
},
});
useEffect(() => {
if (message) {
upsertRecordsInStore({ partialRecords: [message] });
}
}, [message, upsertRecordsInStore]);
if (isDefined(error)) {
if (CombinedGraphQLErrors.is(error)) {
const shouldHideMessageContent = error.errors.some(
@@ -144,6 +144,7 @@ export const useAgentChatData = () => {
},
);
// TODO: Refactor this useEffect to avoid unnecessary re-renders (see PR #18584 review)
useEffect(() => {
if (!threadsData) return;
@@ -204,6 +205,7 @@ export const useAgentChatData = () => {
skip: !isDefined(currentAIChatThread) || isNewThread,
});
// TODO: Refactor this useEffect to avoid unnecessary re-renders (see PR #18584 review)
useEffect(() => {
if (data) {
store.set(skipMessagesSkeletonUntilLoadedState.atom, false);
@@ -37,6 +37,7 @@ export const useWorkspaceFromInviteHash = () => {
}
}, [error, enqueueErrorSnackBar, navigate]);
// TODO: Rework this useEffect - Charles will refactor as part of auth rework
useEffect(() => {
if (!workspaceFromInviteHash || hasRedirected) return;
@@ -44,6 +44,7 @@ export const useGetPublicWorkspaceDataByDomain = () => {
},
);
// TODO: Refactor these useEffects to avoid unnecessary re-renders (see PR #18584 review)
useEffect(() => {
if (data) {
setWorkspaceAuthProviders(
@@ -20,6 +20,7 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField';
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -195,7 +196,7 @@ export const useCreateManyRecords = <
},
},
update: (cache, { data }) => {
const records = (data as Record<string, any>)?.[
const records = (data as Record<string, RecordGqlNode[]> | null)?.[
mutationResponseField
];
@@ -254,8 +255,9 @@ export const useCreateManyRecords = <
});
return (
(createdObjects.data as Record<string, any>)?.[mutationResponseField] ??
[]
(createdObjects.data as Record<string, RecordGqlNode[]> | null)?.[
mutationResponseField
] ?? []
);
};
@@ -103,6 +103,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
client: apolloCoreClient,
});
// TODO: Refactor these useEffects to avoid unnecessary re-renders (see PR #18584 review)
useEffect(() => {
if (data) {
handleFindManyRecordsCompleted(data);
@@ -5,6 +5,7 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types';
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -58,7 +59,9 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
}).retain();
if (result.data) {
const record = getRecordFromRecordNode<T>({
recordNode: (result.data as Record<string, any>)[objectNameSingular],
recordNode: (result.data as Record<string, RecordGqlNode>)[
objectNameSingular
],
});
onCompleted?.(record);
}
@@ -66,6 +69,9 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
called,
error,
loading,
record: (data as Record<string, any>)?.[objectNameSingular] || undefined,
record:
(data as Record<string, RecordGqlNode> | undefined)?.[
objectNameSingular
] || undefined,
};
};
@@ -72,6 +72,7 @@ export const useSingleRecordPickerPerformSearch = ({
[selectedRecords, filteredSelectedRecords, recordsToSelect],
);
// TODO: Refactor this useEffect to avoid unnecessary re-renders (see PR #18584 review)
useEffect(() => {
allSearchRecords.forEach((searchRecord) => {
store.set(
@@ -55,10 +55,12 @@ export const useLoadCurrentUser = () => {
fetchPolicy: 'network-only',
});
const coreViewsResult = await client.query({
query: FindAllCoreViewsDocument,
fetchPolicy: 'network-only',
});
const coreViewsResult = isOnAWorkspace
? await client.query({
query: FindAllCoreViewsDocument,
fetchPolicy: 'network-only',
})
: undefined;
if (isDefined(currentUserResult.error)) {
throw new Error(currentUserResult.error.message);
@@ -137,7 +139,7 @@ export const useLoadCurrentUser = () => {
});
}
if (isDefined(coreViewsResult.data?.getCoreViews)) {
if (isDefined(coreViewsResult?.data?.getCoreViews)) {
setCoreViews(coreViewsResult.data.getCoreViews);
}