Using useState without initializing it properly

Loading

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

  1. Always initialize state with a proper default value
  2. Match the expected type of the state variable
  3. Use TypeScript to catch initialization errors early
  4. Consider null states explicitly when data loads asynchronously
  5. 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

Leave a Reply

Your email address will not be published. Required fields are marked *