State management in Twenty codebase — Part 1.1
Inspired by BulletProof React, I applied its codebase architecture concepts to the Twenty codebase.
This article focuses only on the state management in Twenty codebase.
Prerequisite
- State management in Twenty codebase — Part 1.0
Approach
The approach we take is simple:
-
Pick a route, for example, {id}.twenty.com/settings/profile
-
Locate this route in Twenty codebase.
-
Review how the state is managed.
-
We repeat this process for 3+ pages to establish a common pattern, see if there’s any exceptions.
In this part 1.1, you will learn about the state management in the settings/general route for a workspace. We will find out what libraries Twenty used, how the files are structured, how the data flows to manage its state.

I reviewed the settings/profile route, I found that the following components give us a clear picture about the state management.
Please note that there are other components in this SettingsProfile component that make up the below route, but we are analyzing 3 components in this route.

SettingsProfile
SettingsProfile.tsx has 98LOC and uses the following components to render the page.
-
WorkspaceMemberPictureUploader
-
NameFields
-
EmailField
-
SettingsCard
-
SetOrChangePassword
-
DeleteAccount
These are individual components that are rendered in this /profile page.
WorkspaceMemberPictureUploader
WorkspaceMemberPictureUploader.tsx has 144LOC. This handles picture upload for the workspace.
The following code handles uploading the file:
... const [uploadPicture] = useMutation( UploadWorkspaceMemberProfilePictureDocument, ); const { updateOneRecord } = useUpdateOneRecord(); ... const handleUpload = async (file: File) => { if (isUndefinedOrNull(file) || !canEdit) { return; } const controller = new AbortController(); setUploadController(controller); setIsUploading(true); setErrorMessage(null); let newAvatarUrl: string | null = null; try { const { data } = await uploadPicture({ variables: { file }, context: { fetchOptions: { signal: controller.signal, }, }, }); const signedFile = data?.uploadWorkspaceMemberProfilePicture; if (!isDefined(signedFile)) { throw new Error('Avatar upload failed'); } await updateOneRecord({ objectNameSingular: CoreObjectNameSingular.WorkspaceMember, idToUpdate: workspaceMemberId, updateOneRecordInput: { avatarUrl: signedFile.url }, }); newAvatarUrl = signedFile.url; if (isEditingSelf && isDefined(currentWorkspaceMember)) { setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: newAvatarUrl, }); } if (isDefined(onAvatarUpdated)) { onAvatarUpdated(newAvatarUrl); } setUploadController(null); setErrorMessage(null); } catch (error) { const message = error instanceof Error ? error.message : t`Failed to upload picture`; setErrorMessage(t`An error occurred while uploading the picture.`); enqueueErrorSnackBar({ message }); } finally { setIsUploading(false); } };
Once the upload is complete, there’s a check if the file exists. So how is the state managed here?
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useAtomState( currentWorkspaceMemberState, ); ... newAvatarUrl = signedFile.url; if (isEditingSelf && isDefined(currentWorkspaceMember)) { setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: newAvatarUrl, }); }
When you use setCurrentWorkspaceMember, this state is also available in other parts of your codebase because Twenty uses Jotai to manage state and useAtomState is a hook defined in jotai/hooks/useAtomState.
NameFields
Let’s find out how the NameFields are updated. You will find the below code in NameFields.tsx
const [firstName, setFirstName] = useState( currentWorkspaceMember?.name?.firstName ?? '', ); const [lastName, setLastName] = useState( currentWorkspaceMember?.name?.lastName ?? '', ); const { updateOneRecord } = useUpdateOneRecord(); // TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com) const debouncedUpdate = useDebouncedCallback(async () => { try { if (!currentWorkspaceMember?.id) { throw new Error('User is not logged in'); } if (autoSave) { await updateOneRecord({ objectNameSingular: CoreObjectNameSingular.WorkspaceMember, idToUpdate: currentWorkspaceMember?.id, updateOneRecordInput: { name: { firstName: firstName, lastName: lastName, }, }, }); setCurrentWorkspaceMember({ ...currentWorkspaceMember, name: { firstName, lastName, }, }); } } catch (error) { logError(error); } }, 500);
useUpdateOneRecord is commonly used here. There is a debounced update to the name fields, first and last names. The state is set again using the setCurrentWorkspaceMember defined as shown below:
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useAtomState( currentWorkspaceMemberState, );
This is a consistent pattern that can be reused in the other components within this route or in other settings related routes.
About me:
Hey, my name is Ramu Narasinga. Email: ramu.narasinga@gmail.com
Tired of AI slop?
I spent 3+ years studying OSS codebases and wrote 350+ articles on what makes them production-grade. I built an open source tool that reviews your PR against your existing codebase patterns.
Your codebase. Your patterns. Enforced.