Skip to content

Instantly share code, notes, and snippets.

@sorrycc
Created November 11, 2025 07:32
Show Gist options
  • Select an option

  • Save sorrycc/a80674c9a783fd7fdc554176ee407cbc to your computer and use it in GitHub Desktop.

Select an option

Save sorrycc/a80674c9a783fd7fdc554176ee407cbc to your computer and use it in GitHub Desktop.
# Inline Name Editing on /me Page
**Date:** 2025-11-11
**Feature:** Support user to update name inline on the /me page
---
## Requirements
- **Interaction Pattern:** Always editable input field with auto-save on blur
- **Validation:** Required field with 1-50 character limit
- **Error Handling:** Show inline error message below input and keep invalid value for correction
---
## Architecture Overview
The implementation adds inline editing to the name field using client-side state management with explicit save/revert behavior.
### Key Components
- **UI Layer:** Replace the read-only `<p>` tag with an `<Input>` component that's always visible
- **State Management:** Three local states in the component:
- `editingName`: current draft value in the input
- `savedName`: last successfully saved value (for revert on error)
- `nameError`: validation/API error message (null when valid)
- **API Layer:** New endpoint `src/app/api/me/name/route.ts` handling PATCH requests
- **Database:** Update via Drizzle ORM to users table
### User Flow
1. User sees name in an always-editable input field
2. User types to modify the name
3. On blur (clicking away), trigger validation
4. If valid: call API, update session, clear error
5. If invalid/failed: show error below input, revert to `savedName`
### Visual Feedback
- Subtle border styling to indicate it's editable
- Loading state during API call (disabled input + spinner)
- Inline error message in red text below input when validation/save fails
---
## Component Implementation Details
### State & Initialization
```typescript
const [editingName, setEditingName] = useState(session.user.name || '')
const [savedName, setSavedName] = useState(session.user.name || '')
const [nameError, setNameError] = useState<string | null>(null)
const [isSavingName, setIsSavingName] = useState(false)
```
### Validation Logic
- Check length: 1-50 characters
- Trim whitespace before validation
- Show error: "Name must be between 1 and 50 characters"
### onBlur Handler Flow
1. Check if value changed (compare with `savedName`)
2. If unchanged, do nothing
3. Trim and validate the input
4. If invalid: set `nameError`, revert `editingName` to `savedName`
5. If valid: call API endpoint
6. On API success: update `savedName`, refresh session via `update()`, clear error
7. On API failure: set `nameError` with server message, revert `editingName` to `savedName`
### UI Replacement
Replace the current name display section with an Input component, include:
- Label stays the same
- Input with value bound to `editingName`
- onChange updates `editingName`
- onBlur triggers save handler
- Disabled state when `isSavingName` is true
- Error message div below input (only shown when `nameError` exists)
---
## API Endpoint Implementation
### New File
`src/app/api/me/name/route.ts`
### Endpoint
`PATCH /api/me/name`
### Request Body
```typescript
{ name: string }
```
### Implementation Flow
1. Get session via `getServerSession(authOptions)`
2. If no session: return 401 Unauthorized
3. Extract and trim name from request body
4. Validate: 1-50 characters
5. If invalid: return 400 with error message
6. Update database using Drizzle:
```typescript
await db.update(users)
.set({ name: trimmedName })
.where(eq(users.email, session.user.email))
```
7. Return 200 with success message and updated name
### Error Handling
- 401: "Unauthorized"
- 400: "Name must be between 1 and 50 characters"
- 500: "Failed to update name" (for database errors)
### Response Format
```typescript
{ success: true, name: string, message?: string }
```
---
## Error Handling & Edge Cases
### Client-Side Error Scenarios
1. **Validation Error (empty/too long):**
- Show inline error below input
- Revert input to `savedName`
- User can try again by clicking into input
2. **Network/API Error:**
- Show inline error with server message
- Revert input to `savedName`
- Input remains enabled for retry
3. **Session Refresh After Success:**
- Call `await update()` to sync NextAuth session
- This ensures session.user.name reflects the new value
- Handles case where user navigates away before session updates
### Edge Cases
- **Rapid blur events:** Disable input during save (`isSavingName`) to prevent concurrent updates
- **Session expires during edit:** API returns 401, error shown inline
- **Name unchanged:** Skip API call if `editingName === savedName` after trim
- **Component unmounts during save:** React handles cleanup, no special action needed
### Visual States Summary
- Default: Input with subtle border
- Focused: Input with highlighted border (existing Input component styling)
- Saving: Disabled input with loading spinner/text
- Error: Red text below input with error message
- Success: Input returns to default state with new saved value
---
## Decision Log
### Approach Selection: Client-Side State Management
**Chosen:** Approach A - Client-Side State Management
**Alternatives Considered:**
- **Approach B:** Optimistic Update with Rollback - More complex, better UX
- **Approach C:** Extend Existing `/api/me` Endpoint - More RESTful
**Rationale:** Simple, predictable implementation with clear save/error states. Session refresh via `update()` handles state synchronization adequately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment