# Edit profile screen

> Edit your own profile: change the photo from an action sheet (take, choose, remove) and watch the new one spring in, fields that validate as you leave them, Save with its own pending, saved and error states riding on the keyboard, and an alert that guards unsaved changes on back.

- Type: Component (screens)
- Access: Pro
- Page: https://goodui.dev/native/components/edit-profile-screen
- Markdown: https://goodui.dev/native/components/edit-profile-screen.md
- Registry item: https://goodui.dev/native/r/edit-profile-screen.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native edit profile screen, profile form, expo account form template, change profile photo action sheet, form validation react native, unsaved changes alert, keyboard aware form

## When to use

- Editing the signed-in person's public profile: name, handle, contact email, website and bio.
- As the starting point for any short account form that needs validation, an async save and an unsaved-changes guard.

## When not to use

- Another person's profile: use profile-screen.
- Toggles and preferences that save on their own: use settings-screen; a form with Save is for text people type.
- Long multi-step sign up: split it into screens (sign-up-screen) instead of one long form.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add @goodui-native-pro/edit-profile-screen
npx expo install --fix
```

Pro: add `"@goodui-native-pro": { "url": "https://goodui.dev/native/r/pro/{name}.json", "headers": { "Authorization": "Bearer ${GOODUI_LICENSE_KEY}" } }` to the `registries` of components.json and put your license key in .env.local.

The component lands in `components/goodui-native/edit-profile-screen/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs action-sheet, alert-dialog, avatar, button, large-title-header, locale, text-field.

### Setup

- Wrap the app in GestureHandlerRootView and ThemeProvider (the sheet and the alert render in its overlay layer).

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
// app/settings/edit-profile.tsx
import { useState } from "react";
import { Stack, router } from "expo-router";
import { usePreventRemove } from "@react-navigation/native";
import * as ImagePicker from "expo-image-picker";
import { EditProfileScreen } from "@/components/goodui-native/edit-profile-screen/edit-profile-screen";
import { api, useAccount } from "@/lib/account";

const pickFrom = async (camera: boolean) => {
  const options = { mediaTypes: ["images"], allowsEditing: true, aspect: [1, 1], quality: 0.8 } as const;
  const result = camera ? await ImagePicker.launchCameraAsync(options) : await ImagePicker.launchImageLibraryAsync(options);
  return result.canceled ? null : result.assets[0].uri;
};

export default function EditProfileRoute() {
  const account = useAccount();
  const [dirty, setDirty] = useState(false);
  // Blocks the swipe-back gesture while there are unsaved changes; the screen's own back button shows the alert.
  usePreventRemove(dirty, () => {});
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <EditProfileScreen
        initialValues={{
          name: account.name,
          username: account.username,
          email: account.email,
          website: account.website ?? "",
          bio: account.bio ?? "",
          avatar: account.photoUrl ?? null,
        }}
        onSave={async (values) => {
          const res = await api.updateProfile(values);
          if (res.usernameTaken) throw { fieldErrors: { username: "That username is taken" } };
        }}
        onTakePhoto={() => pickFrom(true)}
        onChoosePhoto={() => pickFrom(false)}
        onDirtyChange={setDirty}
        onBack={() => router.back()}
      />
    </>
  );
}
```

## Examples

### On its own, with a sample profile

```tsx
<EditProfileScreen onBack={() => router.back()} />
```

### Show a server error under a field

```tsx
<EditProfileScreen
  onSave={(values) =>
    api.updateProfile(values).catch(() => Promise.reject({ fieldErrors: { email: "This email belongs to another account" } }))
  }
/>
```

## API reference

### EditProfileScreen

`import { EditProfileScreen } from "@/components/goodui-native/edit-profile-screen/edit-profile-screen";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `initialValues` | `EditProfileValues` | `sampleValues` | The saved profile the form starts from. |
| `onSave` | `(values: EditProfileValues) => unknown` | – | Save was pressed with valid fields. Return a promise: the button gathers into a spinner, then shows Saved (the form becomes clean) or an error. Reject with `{ fieldErrors }` to put messages under fields. |
| `onBack` | `() => void` | – | Back was pressed. With unsaved changes, an alert asks to keep editing or discard first. |
| `onTakePhoto` | `() => PickResult \| Promise<PickResult>` | – | "Take photo" in the photo sheet. Return (or resolve with) the new image source, or nothing when cancelled. |
| `onChoosePhoto` | `() => PickResult \| Promise<PickResult>` | – | "Choose from library" in the photo sheet. Return (or resolve with) the image source, or nothing when cancelled. |
| `onDirtyChange` | `(dirty: boolean) => void` | – | Called when the form becomes dirty or clean, for example to block the navigator's back gesture. |
| `title` | `string` | – | Title. Default "Edit profile". Same as `labels.title`. |
| `labels` | `Partial<EditProfileScreenLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- Every field has a visible label; errors replace the helper text, are announced when they appear and are part of the field's hint.
- Return moves to the next field; Save reports disabled while there is nothing to save and busy while saving, and its result is announced.
- The photo sheet and the discard alert render in the overlay layer; the sheet closes with Escape or Cancel, the alert needs an answer (Keep editing or Discard).
- The back button is named 'Back'. On Android the hardware back button opens the discard alert while there are unsaved changes.

## Motion

- A new or removed photo springs in from 0.7 scale with one overshoot as it fades in.
- Save gathers into a spinner, then opens into Saved with a drawn check, or shakes into the error label.
- Field labels glide up on focus; an error slides in and the field shakes once.
- Save rides on top of the keyboard with the keyboard's own duration, and the focused field scrolls into view (native); the title collapses into the bar as the form scrolls.
- Reduced motion: the photo fades in without scaling, the button and fields change without shaking, scrolling and the title change without travel.

## Haptics

- tap on Save and on the photo button; success or error when the save settles (Button)
- error when a field shows an error, warning when the bio reaches 150 characters (TextField)
- tap when a photo sheet action or an alert action is chosen

## Performance

- Validation is synchronous and runs on change only for fields already touched; the keyboard offset and every animation run on the UI thread.

## Notes for AI

- initialValues is the saved profile; avatar is an image source or null. The form is dirty when any field or the photo differs from it, and clean again after a successful save.
- onSave receives trimmed values. Return a promise: resolve to show Saved, reject to show the error; reject with { fieldErrors: { username: '…' } } to put messages under fields.
- onTakePhoto and onChoosePhoto return or resolve with an image source (for example an expo-image-picker uri), or null when cancelled. Only the actions you pass appear in the sheet; Remove photo appears when there is a photo.
- The alert only guards the screen's own back button and Android's hardware back. To guard the iOS swipe-back gesture, keep onDirtyChange in state and pass it to usePreventRemove from @react-navigation/native in the route.
- Usernames are lowercased as typed and must be 3 to 24 letters, numbers, periods or underscores; the website is optional; the bio stops at 150 characters.
- Render it full-bleed with the navigator header hidden (headerShown: false); it handles the safe areas and the keyboard itself, so do not wrap it in KeyboardAvoidingView.

## Related

- [Text field](https://goodui.dev/native/components/text-field.md): A text field whose label glides up as you focus it, with helper and error text that slide in, a shake and haptic on error, clear and reveal buttons, a character counter and a multiline mode that grows with its text.
- [Action sheet](https://goodui.dev/native/components/action-sheet.md): An iOS-style list of choices that springs up from the bottom edge in grouped cards; it follows the finger to be dragged away, and pulling it up stretches the gap between the cards before they spring back together.
- [Alert dialog](https://goodui.dev/native/components/alert-dialog.md): A centered alert that springs in from slightly larger; touch an action and slide across the others with a tick per action, tap outside and it nudges, and async actions keep it open with a spinner until they settle.
- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
- [Avatar](https://goodui.dev/native/components/avatar.md): A photo that fades in over initials on a color picked from the name, with a presence dot and a story ring whose accent arc sweeps in when there is something new and away once seen; AvatarGroup stacks people with a +N counter and fans out on tap.
- [Settings screen](https://goodui.dev/native/components/settings-screen.md): A complete settings screen in inset grouped sections: account, notification switches that flip optimistically, a light, dark and system appearance switch, privacy, storage with a cache size that rolls down to zero when cleared, about, and a destructive sign out confirmed in an alert that runs it.
- [Profile screen](https://goodui.dev/native/components/profile-screen.md): A complete social profile: the handle in the bar, an avatar with a story ring that sweeps to seen, post, follower and following counts that roll up from zero as the screen opens, bio, location and link, Follow and Message buttons, and swipeable tabs with a photo grid that opens full screen and a list.

## Guidance for AI tools

Edit profile screen: Edit your own profile: change the photo from an action sheet (take, choose, remove) and watch the new one spring in, fields that validate as you leave them, Save with its own pending, saved and error states riding on the keyboard, and an alert that guards unsaved changes on back. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
