React Conditional Rendering - Complete Guide

React Conditional Rendering - Complete Guide

Conditional rendering is a fundamental concept in React that lets you create components that can render different things based on different conditions. This guide covers all the ways to conditionally render content in React applications.

What is Conditional Rendering?

Conditional rendering means rendering different JSX elements or components based on certain conditions. Just like JavaScript uses if/else statements or switch statements to control program flow, React uses conditional rendering to control what gets displayed on the screen.

Basic Conditional Rendering Methods

1. Using the if Statement

The simplest way to conditionally render content is using a regular JavaScript if statement:

function UserGreeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  return <h1>Please sign in.</h1>;
}

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  return (
    <div>
      <UserGreeting isLoggedIn={isLoggedIn} />
      <button onClick={() => setIsLoggedIn(!isLoggedIn)}>
        {isLoggedIn ? 'Sign Out' : 'Sign In'}
      </button>
    </div>
  );
}

2. Using the Ternary Operator

For inline conditional rendering, the ternary operator is perfect:

function Message({ showMessage }) {
  return (
    <div>
      {showMessage ? (
        <p>This message is visible!</p>
      ) : (
        <p>This message is hidden.</p>
      )}
    </div>
  );
}

function App() {
  const [showMessage, setShowMessage] = useState(true);
  
  return (
    <div>
      <Message showMessage={showMessage} />
      <button onClick={() => setShowMessage(!showMessage)}>
        Toggle Message
      </button>
    </div>
  );
}

3. Using Logical AND (&&) Operator

The logical AND operator is great for rendering something only when a condition is true:

function Notification({ hasNewMessages, messageCount }) {
  return (
    <div>
      <h2>Notifications</h2>
      {hasNewMessages && (
        <p>You have {messageCount} new messages!</p>
      )}
    </div>
  );
}

function App() {
  const [hasNewMessages, setHasNewMessages] = useState(true);
  const messageCount = 5;
  
  return (
    <div>
      <Notification 
        hasNewMessages={hasNewMessages} 
        messageCount={messageCount} 
      />
      <button onClick={() => setHasNewMessages(!hasNewMessages)}>
        Toggle Notifications
      </button>
    </div>
  );
}

Advanced Conditional Rendering Patterns

1. Element Variables

You can store JSX elements in variables and use them in your render method:

function LoginControl() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  let button;
  if (isLoggedIn) {
    button = <LogoutButton onClick={() => setIsLoggedIn(false)} />;
  } else {
    button = <LoginButton onClick={() => setIsLoggedIn(true)} />;
  }
  
  return (
    <div>
      <Greeting isLoggedIn={isLoggedIn} />
      {button}
    </div>
  );
}

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  return <h1>Please sign in.</h1>;
}

function LoginButton({ onClick }) {
  return <button onClick={onClick}>Login</button>;
}

function LogoutButton({ onClick }) {
  return <button onClick={onClick}>Logout</button>;
}

2. Immediately Invoked Function Expressions (IIFE)

For more complex conditions, you can use IIFEs:

function UserStatus({ user, isLoading }) {
  return (
    <div>
      {(() => {
        if (isLoading) {
          return <div>Loading user data...</div>;
        }
        
        if (!user) {
          return <div>No user data available</div>;
        }
        
        if (user.isActive) {
          return <div className="status-active">User is active</div>;
        } else {
          return <div className="status-inactive">User is inactive</div>;
        }
      })()}
    </div>
  );
}

3. Switch Statement Pattern

For multiple conditions, a switch statement can be cleaner:

function StatusMessage({ status }) {
  const getStatusMessage = () => {
    switch (status) {
      case 'loading':
        return <div>Loading...</div>;
      case 'success':
        return <div className="success">Operation completed successfully!</div>;
      case 'error':
        return <div className="error">An error occurred. Please try again.</div>;
      case 'warning':
        return <div className="warning">Please review your input.</div>;
      default:
        return <div>Unknown status</div>;
    }
  };
  
  return (
    <div>
      <h2>Status</h2>
      {getStatusMessage()}
    </div>
  );
}

Conditional Rendering with Components

1. Component Wrappers

Create wrapper components that handle conditional logic:

function ConditionalWrapper({ condition, wrapper, children }) {
  return condition ? wrapper(children) : children;
}

function App() {
  const [isLink, setIsLink] = useState(false);
  
  return (
    <div>
      <ConditionalWrapper
        condition={isLink}
        wrapper={children => <a href="#">{children}</a>}
      >
        <p>This content might be wrapped in a link</p>
      </ConditionalWrapper>
      
      <button onClick={() => setIsLink(!isLink)}>
        Toggle Link Wrapper
      </button>
    </div>
  );
}

2. Higher-Order Components

Use HOCs for conditional rendering logic:

function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    
    if (!isAuthenticated) {
      return <div>Please log in to access this content.</div>;
    }
    
    return <WrappedComponent {...props} />;
  };
}

function SecretContent() {
  return <div>This is secret content only for authenticated users!</div>;
}

const ProtectedSecretContent = withAuth(SecretContent);

Common Use Cases

1. Loading States

Handle different loading and error states:

function DataLoader({ url }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
        setError(null);
      } catch (err) {
        setError(err.message);
        setData(null);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  if (loading) {
    return <div className="loading">Loading data...</div>;
  }
  
  if (error) {
    return <div className="error">Error: {error}</div>;
  }
  
  if (!data || data.length === 0) {
    return <div className="no-data">No data available</div>;
  }
  
  return (
    <div className="data-container">
      {data.map(item => (
        <div key={item.id} className="data-item">
          {item.name}
        </div>
      ))}
    </div>
  );
}

2. Form Validation

Show different UI based on form validation state:

function FormValidation() {
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  });
  const [errors, setErrors] = useState({});
  const [isSubmitted, setIsSubmitted] = useState(false);
  
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    
    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters';
    }
    
    return newErrors;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = validateForm();
    
    if (Object.keys(newErrors).length === 0) {
      setIsSubmitted(true);
      setErrors({});
    } else {
      setErrors(newErrors);
    }
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };
  
  if (isSubmitted) {
    return (
      <div className="success-message">
        <h2>Form submitted successfully!</h2>
        <p>Email: {formData.email}</p>
      </div>
    );
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="form-group">
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          className={errors.email ? 'error' : ''}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>
      
      <div className="form-group">
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          className={errors.password ? 'error' : ''}
        />
        {errors.password && <span className="error-message">{errors.password}</span>}
      </div>
      
      <button type="submit">Submit</button>
    </form>
  );
}

3. Feature Flags

Control features based on configuration:

function FeatureFlaggedComponent({ featureFlags }) {
  return (
    <div>
      <h1>Application</h1>
      
      {/* Basic feature - always available */}
      <div className="basic-feature">
        <h2>Basic Feature</h2>
        <p>This feature is available to all users.</p>
      </div>
      
      {/* Premium feature - only for premium users */}
      {featureFlags.isPremium && (
        <div className="premium-feature">
          <h2>Premium Feature</h2>
          <p>This feature is only available to premium users.</p>
        </div>
      )}
      
      {/* Beta feature - only for beta testers */}
      {featureFlags.isBetaTester && (
        <div className="beta-feature">
          <h2>Beta Feature</h2>
          <p>This feature is currently in beta.</p>
        </div>
      )}
      
      {/* Admin features - only for admins */}
      {featureFlags.isAdmin && (
        <div className="admin-feature">
          <h2>Admin Panel</h2>
          <p>Administrative controls and settings.</p>
        </div>
      )}
    </div>
  );
}

Performance Considerations

1. Avoiding Unnecessary Renders

Use React.memo to prevent unnecessary re-renders:

const ExpensiveComponent = React.memo(({ data, isVisible }) => {
  console.log('ExpensiveComponent rendered');
  
  return (
    <div>
      {isVisible && (
        <div>
          {data.map(item => (
            <div key={item.id}>
              {item.name} - {item.value}
            </div>
          ))}
        </div>
      )}
    </div>
  );
});

2. Lazy Loading Components

Load components only when needed:

import React, { lazy, Suspense } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  const [showComponent, setShowComponent] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowComponent(true)}>
        Load Component
      </button>
      
      {showComponent && (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </Suspense>
      )}
    </div>
  );
}

Best Practices

1. Keep Conditional Logic Simple

// Bad - complex inline logic
return (
  <div>
    {user && user.isActive && user.permissions.includes('admin') && !user.isBanned ? (
      <AdminPanel />
    ) : (
      <UserPanel />
    )}
  </div>
);

// Good - extract to a variable or function
function App({ user }) {
  const canAccessAdmin = user && 
    user.isActive && 
    user.permissions.includes('admin') && 
    !user.isBanned;
  
  return (
    <div>
      {canAccessAdmin ? <AdminPanel /> : <UserPanel />}
    </div>
  );
}

2. Use Descriptive Variable Names

// Bad
return (
  <div>
    {a && b ? <Component1 /> : <Component2 />}
  </div>
);

// Good
return (
  <div>
    {isLoggedIn && hasPermission ? <AdminDashboard /> : <UserDashboard />}
  </div>
);

3. Handle Edge Cases

function UserList({ users }) {
  // Handle null/undefined
  if (!users) {
    return <div>No user data available</div>;
  }
  
  // Handle empty array
  if (users.length === 0) {
    return <div>No users found</div>;
  }
  
  // Render users
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Summary

Conditional rendering is essential for building dynamic React applications. Remember these key points:

  • Use if statements for complex conditions outside JSX
  • Use ternary operators for simple either/or conditions
  • Use logical AND (&&) for showing/hiding elements
  • Extract complex logic into variables or functions
  • Handle loading, error, and empty states
  • Consider performance when conditionally rendering expensive components
  • Keep conditional logic readable and maintainable

Mastering conditional rendering will help you create more flexible and user-friendly React applications that can adapt to different states and conditions.


External Resources:

Related Tutorials:

Last updated on