Proper Initialization of useState in React
A common React mistake is not properly initializing state when using the useState
hook, which can lead to undefined values and runtime errors.
The Problem: Uninitialized or Poorly Initialized State
// ❌ No initial value (undefined)
const [user, setUser] = useState();
// ❌ Inconsistent type initialization
const [count, setCount] = useState(); // Later expects number
Correct Initialization Patterns
1. Basic Initialization
// ✅ Primitive values
const [count, setCount] = useState(0); // Number
const [name, setName] = useState(''); // String
const [isActive, setIsActive] = useState(false); // Boolean
2. Object Initialization
// ✅ Objects
const [user, setUser] = useState({
id: null,
name: '',
email: ''
});
// ✅ Alternative empty object
const [formData, setFormData] = useState({});
3. Array Initialization
// ✅ Arrays
const [items, setItems] = useState([]);
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', completed: false }
]);
Advanced Initialization Techniques
1. Lazy Initialization (for expensive computations)
// ✅ Function form for expensive initial state
const [data, setData] = useState(() => {
const initialData = calculateExpensiveValue();
return initialData;
});
2. TypeScript Typed State
// ✅ With TypeScript
interface User {
id: number;
name: string;
email?: string;
}
const [user, setUser] = useState<User>({
id: 0,
name: ''
});
3. Initializing from Props
function Counter({ initialCount }) {
// ✅ Initialize from props
const [count, setCount] = useState(initialCount || 0);
// ✅ Better - explicit default
const [count, setCount] = useState(() => initialCount ?? 0);
}
Common Pitfalls
1. Undefined Initial State
// ❌ Problematic
const [data, setData] = useState(); // data is undefined
// Later in code:
return <div>{data.name}</div>; // TypeError!
2. Inconsistent Types
// ❌ Starts as null, expects array
const [items, setItems] = useState(null);
// Later:
items.map(item => ...) // TypeError!
3. Missing Required Fields
interface Product {
id: string;
name: string;
price: number;
}
// ❌ Missing required fields
const [product, setProduct] = useState<Product>({}); // Error
Best Practices
- Always initialize state with a proper default value
- Match the expected type of the state variable
- Use TypeScript to catch initialization errors early
- Consider null states explicitly when data loads asynchronously
- Document expected shape of complex state objects
// ✅ Good practice - explicit loading state
const [user, setUser] = useState<null | User>(null);
// In component:
if (user === null) return <LoadingSpinner />;
return <Profile user={user} />;
Real-World Example
function UserForm() {
// ✅ Properly initialized form state
const [form, setForm] = useState({
username: '',
password: '',
rememberMe: false
});
// ✅ Properly initialized API state
const [apiState, setApiState] = useState({
loading: false,
error: null,
data: null
});
const handleSubmit = async (e) => {
e.preventDefault();
setApiState(prev => ({ ...prev, loading: true }));
try {
const response = await login(form);
setApiState({ loading: false, error: null, data: response });
} catch (error) {
setApiState({ loading: false, error, data: null });
}
};
// ...
}
Remember that proper state initialization:
- Prevents undefined/null reference errors
- Makes component behavior more predictable
- Helps TypeScript (if used) provide better type checking
- Documents your expected state shape for other developers