From 6db0051ed246efadda8d6e5b47a4fb637511da27 Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Wed, 5 Aug 2026 12:10:45 -0700 Subject: [PATCH] fix(profile): preserve label account type across page refreshes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs caused profile_type to revert to null after switching to label: 1. useUpdateProfile had no onSuccess handler, so the server-confirmed data returned by mutationFn was discarded entirely. The optimistic update was left in cache but nothing re-confirmed it after the write landed. Fix: add onSuccess that re-applies the mutation variables (not the raw server response) to the query cache with forceReplace: true. Using variables rather than the server response guards against discovery-node propagation lag — the getUser call may hit a node that has not yet indexed the confirmed block. 2. onError rolled back with { ...context.previousMetadata, ...metadata } instead of context.previousMetadata alone, effectively applying the failed mutation on top of the pre-mutation state instead of restoring it. Fix: roll back with context.previousMetadata only. 3. setLocalStorageAccountAndUser (called inside fetchAccountAsync on every page load) wrote the server-fetched accountUser directly to localStorage, overwriting any recent optimistic updates such as profile_type. On the next reload, fetchLocalAccountAsync would read this stale localStorage value and prime the cache with profile_type: null, reverting the user to artist. Fix: prefer the React Query cache over the server response when writing to localStorage. The cache already holds the correct post-mutation state; the server response may lag due to node propagation. If the cache is empty (first load), fall back to the server value as before. --- .../api/tan-query/users/useUpdateProfile.ts | 22 ++++++++++++++++--- packages/common/src/store/account/sagas.ts | 13 ++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/common/src/api/tan-query/users/useUpdateProfile.ts b/packages/common/src/api/tan-query/users/useUpdateProfile.ts index 61e04fcaeb9..51a878060e3 100644 --- a/packages/common/src/api/tan-query/users/useUpdateProfile.ts +++ b/packages/common/src/api/tan-query/users/useUpdateProfile.ts @@ -81,12 +81,28 @@ export const useUpdateProfile = () => { return { previousMetadata } }, - onError: (error, metadata, context?: MutationContext) => { - // If the mutation fails, roll back user data + onSuccess: (_data, variables) => { + // Re-confirm the mutation in the cache after server success. + // We re-apply mutation variables rather than the raw server response + // to guard against discovery-node propagation lag: the node answering + // getUser may not yet have indexed the confirmed block. + const currentCache = queryClient.getQueryData( + getUserQueryKey(currentUserId) + ) + if (currentCache) { + primeUserData({ + queryClient, + users: [{ ...currentCache, ...variables }], + forceReplace: true + }) + } + }, + onError: (error, _metadata, context?: MutationContext) => { + // If the mutation fails, roll back user data to previous state if (context?.previousMetadata) { primeUserData({ queryClient, - users: [{ ...context.previousMetadata, ...metadata }], + users: [context.previousMetadata], forceReplace: true }) } diff --git a/packages/common/src/store/account/sagas.ts b/packages/common/src/store/account/sagas.ts index 18ee14e2e0a..1a44491a176 100644 --- a/packages/common/src/store/account/sagas.ts +++ b/packages/common/src/store/account/sagas.ts @@ -364,7 +364,18 @@ function* setLocalStorageAccountAndUser( } yield* call([localStorage, localStorage.setAudiusAccount], formattedAccount) - yield* call([localStorage, localStorage.setAudiusAccountUser], accountUser) + // Prefer cached user data (which may include recent optimistic mutations + // like profile_type changes) over the server-fetched response. This prevents + // setLocalStorageAccountAndUser from overwriting changes that haven't yet + // propagated to all discovery nodes. + const queryClient = yield* getContext('queryClient') + const cachedUser = queryClient.getQueryData( + getUserQueryKey(accountUser.user_id) + ) + yield* call( + [localStorage, localStorage.setAudiusAccountUser], + cachedUser ?? accountUser + ) } function* recordIPIfNotRecent(handle: string): SagaIterator {