Setting state from props without validation

Loading

Handling State Derived from Props Safely

A common React anti-pattern is initializing state from props without proper validation or update handling, which can lead to bugs when props change. Here’s how to do it correctly.

The Problematic Pattern

// ❌ Dangerous - copies props to state without validation or updates
class UserProfile extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: props.username,
      email: props.email
    };
  }

  // ...
}

Recommended Solutions

1. For Class Components: Use getDerivedStateFromProps

class UserProfile extends React.Component {
  state = {
    username: '',
    email: ''
  };

  static getDerivedStateFromProps(props, state) {
    return {
      username: props.username || state.username,
      email: props.email || state.email
    };
  }
}

2. For Function Components: Use useEffect with Dependencies

function UserProfile({ username, email }) {
  const [userData, setUserData] = useState({ username: '', email: '' });

  useEffect(() => {
    setUserData({
      username: username || 'Anonymous',
      email: email || 'no-email@example.com'
    });
  }, [username, email]); // Only update when these props change
}

3. With Validation (Recommended)

function UserProfile({ username, email }) {
  const [userData, setUserData] = useState(() => ({
    username: validateUsername(username),
    email: validateEmail(email)
  }));

  useEffect(() => {
    setUserData({
      username: validateUsername(username),
      email: validateEmail(email)
    });
  }, [username, email]);

  function validateUsername(name) {
    if (!name || typeof name !== 'string') return 'Anonymous';
    return name.trim();
  }

  function validateEmail(email) {
    const re = /\S+@\S+\.\S+/;
    return re.test(email) ? email : 'invalid-email@example.com';
  }
}

When to Use Derived State

Only use derived state when:

  1. You need to modify the prop values before using them
  2. You need to maintain local versions that can change independently
  3. You need to compute expensive values from props

Anti-Patterns to Avoid

  1. Unconditional state copying:
   // ❌ Bad - overwrites local state every time props change
   useEffect(() => {
     setUserData({ username, email });
   }, [username, email]);
  1. Ignoring prop changes:
   // ❌ Bad - state becomes stale if props change
   const [userData] = useState({ username, email });
  1. Two-way data binding:
   // ❌ Avoid - makes data flow hard to understand
   // (parent manages some state, child manages other state)

Best Practices

  1. Validate all incoming props before using them
  2. Use memoization for expensive computations:
   const fullName = useMemo(() => {
     return `${firstName} ${lastName}`;
   }, [firstName, lastName]);
  1. Consider lifting state up if synchronization becomes complex
  2. Use TypeScript to catch prop type issues at compile time
  3. Document expected prop shapes with PropTypes or TypeScript interfaces

Remember that in most cases, you don’t need to copy props to state – you can use the props directly. Only use derived state when you specifically need to modify or control the prop values within your component.

Leave a Reply

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