Forgetting default state values in functional components

Loading

A common React mistake is not providing proper default values for state in functional components, which can lead to:

  • undefined state errors
  • Broken component rendering
  • Unexpected runtime behavior
  • Difficult-to-debug issues

The Problem

// ❌ Problematic - no default value for potentially undefined prop
function UserProfile({ user }) {
  const [name, setName] = useState(user.name); // Error if user is undefined

  return <div>{name}</div>;
}

// ❌ Another bad pattern
function Counter() {
  const [count] = useState(); // count will be undefined
  return <div>{count + 1}</div>; // NaN error
}

Correct Solutions

1. Basic Default Values

// ✅ Provide sensible defaults
function UserProfile({ user }) {
  const [name, setName] = useState(user?.name ?? 'Guest');
  return <div>{name}</div>;
}

2. Default Props Pattern

// ✅ Using defaultProps (for class-like consistency)
function UserProfile({ user }) {
  const [name, setName] = useState(user.name);
  return <div>{name}</div>;
}

UserProfile.defaultProps = {
  user: { name: 'Guest' }
};

3. Fallback Objects

// ✅ Safely destructure with fallback
function UserProfile({ user = {} }) {
  const [name, setName] = useState(user.name || 'Guest');
  return <div>{name}</div>;
}

4. TypeScript Solution

// ✅ With TypeScript interfaces
interface User {
  name: string;
  age?: number;
}

function UserProfile({ user = { name: 'Guest' } }: { user?: User }) {
  const [name, setName] = useState(user.name);
  return <div>{name}</div>;
}

Common Scenarios and Fixes

1. Array State

// ❌ Bad
const [items, setItems] = useState(); // items is undefined

// ✅ Good
const [items, setItems] = useState([]); // Default empty array

2. Object State

// ❌ Bad
const [config, setConfig] = useState();

// ✅ Good
const [config, setConfig] = useState({
  theme: 'light',
  notifications: true
});

3. Boolean State

// ❌ Ambiguous
const [isOpen, setIsOpen] = useState();

// ✅ Clear default
const [isOpen, setIsOpen] = useState(false);

Best Practices

  1. Always initialize state – Never leave useState() empty
  2. Match the expected type – Keep consistent data structures
  3. Consider null state – Sometimes better than empty defaults
  4. Document expected shape – With PropTypes or TypeScript
  5. Use destructuring defaults – For props and state

Advanced Patterns

1. Lazy Initial State

// ✅ For expensive initial state
const [data, setData] = useState(() => {
  const initialValue = calculateExpensiveValue(props.someValue);
  return initialValue;
});

2. Custom Hook with Defaults

function useSafeState(initialValue) {
  return useState(initialValue ?? getDefaultValue());
}

// Usage
const [count, setCount] = useSafeState(); // Uses default

3. Reducer Default State

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, initialState);

  • What happens if the prop is undefined?
  • What’s a sensible default for this state?
  • How will this component behave on first render?

Leave a Reply

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