AutoSaveTextField
Text input that automatically saves changes after a delay.
The AutoSaveTextField component automatically saves changes after a configurable delay, providing a seamless editing experience.
Features
- Debounced auto-save
- Save indicator
- Error handling
- MUI TextField integration
- Configurable delay
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | "" | Current value |
onSave | function | required | Called to save value |
delay | number | 1000 | Debounce delay in ms |
label | string | - | Input label |
multiline | boolean | false | Enable multiline |
rows | number | 1 | Number of rows |
...props | - | - | Other MUI TextField props |
Usage
Basic Usage
jsx
import { AutoSaveTextField } from 'authscape/components';import { apiService } from 'authscape';export default function ProfileEditor({ userId }) {const [bio, setBio] = useState('');const handleSave = async (value) => {await apiService().put('/api/Users/UpdateBio', {userId,bio: value});};return (<AutoSaveTextFieldvalue={bio}onSave={handleSave}label="Bio"multilinerows={4}fullWidth/>);}
With Custom Delay
jsx
<AutoSaveTextFieldvalue={notes}onSave={handleSave}delay={2000} // Save after 2 secondslabel="Notes"multilinerows={6}/>
In a Form
jsx
export default function SettingsForm() {const saveField = async (field, value) => {await apiService().put('/api/Settings/Update', { field, value });};return (<form><AutoSaveTextFieldvalue={settings.companyName}onSave={(value) => saveField('companyName', value)}label="Company Name"fullWidthsx={{ mb: 2 }}/><AutoSaveTextFieldvalue={settings.website}onSave={(value) => saveField('website', value)}label="Website"fullWidthsx={{ mb: 2 }}/><AutoSaveTextFieldvalue={settings.description}onSave={(value) => saveField('description', value)}label="Description"multilinerows={4}fullWidth/></form>);}
Implementation
jsx
import { useState, useEffect, useRef, useCallback } from 'react';import { TextField, InputAdornment, CircularProgress } from '@mui/material';import { Check, Error } from '@mui/icons-material';export function AutoSaveTextField({value: initialValue,onSave,delay = 1000,label,...props}) {const [value, setValue] = useState(initialValue);const [status, setStatus] = useState('idle'); // idle, saving, saved, errorconst timerRef = useRef(null);const lastSavedValue = useRef(initialValue);useEffect(() => {setValue(initialValue);lastSavedValue.current = initialValue;}, [initialValue]);const save = useCallback(async (newValue) => {if (newValue === lastSavedValue.current) return;setStatus('saving');try {await onSave(newValue);lastSavedValue.current = newValue;setStatus('saved');setTimeout(() => setStatus('idle'), 2000);} catch (error) {setStatus('error');console.error('Save failed:', error);}}, [onSave]);const handleChange = (e) => {const newValue = e.target.value;setValue(newValue);// Clear existing timerif (timerRef.current) {clearTimeout(timerRef.current);}// Set new timertimerRef.current = setTimeout(() => {save(newValue);}, delay);};// Cleanup on unmountuseEffect(() => {return () => {if (timerRef.current) {clearTimeout(timerRef.current);}};}, []);// Save on blur immediatelyconst handleBlur = () => {if (timerRef.current) {clearTimeout(timerRef.current);}if (value !== lastSavedValue.current) {save(value);}};const getEndAdornment = () => {switch (status) {case 'saving':return (<InputAdornment position="end"><CircularProgress size={20} /></InputAdornment>);case 'saved':return (<InputAdornment position="end"><Check color="success" /></InputAdornment>);case 'error':return (<InputAdornment position="end"><Error color="error" /></InputAdornment>);default:return null;}};return (<TextFieldvalue={value}onChange={handleChange}onBlur={handleBlur}label={label}InputProps={{endAdornment: getEndAdornment()}}{...props}/>);}
With Validation
jsx
export function ValidatedAutoSaveTextField({value,onSave,validate,...props}) {const [error, setError] = useState(null);const handleSave = async (newValue) => {// Validate before savingconst validationError = validate?.(newValue);if (validationError) {setError(validationError);return;}setError(null);await onSave(newValue);};return (<AutoSaveTextFieldvalue={value}onSave={handleSave}error={Boolean(error)}helperText={error}{...props}/>);}// Usage<ValidatedAutoSaveTextFieldvalue={email}onSave={handleSave}validate={(value) => {if (!value.includes('@')) {return 'Invalid email address';}return null;}}label="Email"/>
Best Practices
- Set appropriate delay - Balance responsiveness with API calls
- Save on blur - Don't lose data when user clicks away
- Show save status - Indicate saving, saved, or error
- Handle errors - Show error state and allow retry
- Debounce properly - Avoid multiple simultaneous saves