React Conditional Rendering and Lists
Conditional rendering with if/&&/ternary, rendering lists with map, keys, and filter..
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
- ✓React 18+, Node.js 16+, basic understanding of JSX, state management with useState and useEffect, familiarity with JavaScript array methods (map, filter), and a code editor (VS Code recommended). TypeScript knowledge is helpful but not required.
React Conditional Rendering and Lists: A core React concept for building modern user interfaces. It helps you structure your components efficiently and handle data flow predictably.
React is a JavaScript library for building user interfaces. This article covers conditional lists — a key concept for building modern web applications.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
A comprehensive guide to react conditional rendering and lists with production examples and best practices.
Why Conditional Rendering and Lists Are Core to React
In React, conditional rendering and lists are not just features—they are the backbone of dynamic UIs. Every production app uses them to show or hide elements based on state, and to render collections of data. Without a solid grasp of these patterns, you'll end up with bloated components, unnecessary re-renders, and hard-to-debug bugs. This section sets the stage by explaining why these concepts matter in real-world applications, from dashboards to e-commerce product lists. We'll focus on the mental model: think of your UI as a function of state, where conditionals and loops are just JavaScript expressions inside JSX. The key is to keep them simple and predictable. Avoid complex ternaries or nested conditions that obscure logic. Instead, extract conditions into helper functions or early returns. For lists, always use a stable key and avoid index keys unless the list is static and small. This foundation will save you from performance pitfalls and maintainability nightmares.
Conditional Rendering Patterns: If, Ternary, &&, and Switch
React offers several ways to conditionally render elements. The most common are if statements (outside JSX), ternary operators, logical AND (&&), and switch statements. Each has its place. Use if statements for complex logic or early returns. Use ternaries for simple binary choices inside JSX. Use && for rendering something only when a condition is true—but beware of falsy values like 0 or empty string, which can render unexpected output. Switch statements are rare but useful when you have multiple discrete states. In production, the choice matters for readability and bug prevention. For example, using && with a count of 0 will render '0' instead of nothing. Always coerce to boolean: {!!count && <Component />}. Also, avoid deeply nested ternaries; extract into a variable or a function. This section will show you each pattern with realistic examples and warn you about common pitfalls.
Rendering Lists with map() and Keys
The map() function is the standard way to render lists in React. It transforms an array into an array of React elements. The critical part is the key prop. Keys help React identify which items have changed, been added, or removed. Using index as a key is a common anti-pattern that leads to bugs when the list is reordered or filtered. In production, always use a unique identifier from your data, like an ID. If your data doesn't have one, generate a stable key using a library like uuid or nanoid. Never use Math.random() as keys—they change every render, causing unnecessary re-renders and potential state loss. This section will also cover rendering nested lists and handling empty states. Remember: keys only need to be unique among siblings, not globally. We'll also discuss the importance of keeping list items as pure components to avoid performance issues.
Conditional Rendering with Lists: Filtering and Sorting
Often you need to conditionally render a list based on filters or sort order. This combines conditional logic with list rendering. The cleanest approach is to compute the filtered/sorted list before the JSX, using useMemo to avoid recalculating on every render. For example, a search input filters a list of products. You compute the filtered list with useMemo, then map over it. This keeps your JSX clean and your logic testable. Avoid chaining filter and map inside JSX—it's harder to read and debug. Also, consider debouncing the filter input to avoid performance hits on large datasets. In production, always handle edge cases: empty results, loading state, and error state. This section will walk through a realistic search-and-filter component with proper memoization and error boundaries.
Handling Complex Conditional UI: Multiple States and Error Boundaries
Real apps have multiple states: loading, empty, error, and success. Conditional rendering must handle all of them gracefully. A common pattern is to use a state machine or a reducer to manage these states. For example, a data-fetching component might have states: 'idle', 'loading', 'success', 'error'. You can render different UI for each state using a switch or object map. Additionally, error boundaries can catch rendering errors in child components and display fallback UI. This is especially important for list items that might throw errors due to malformed data. In production, never assume data is well-formed. Always validate and handle errors. This section will show you how to build a robust component that handles all states and uses error boundaries to prevent the entire app from crashing.
Performance Optimization: Virtualization and Memoization
Rendering large lists (thousands of items) can cause performance issues. Virtualization libraries like react-window or react-virtuoso render only the visible items, drastically reducing DOM nodes. For conditional rendering, avoid unnecessary re-renders by using React.memo on list items and useCallback on event handlers. Also, avoid creating new objects or functions in the render cycle. For example, inline arrow functions in list items cause re-renders because they are new references each time. Use useCallback to stabilize them. In production, we've seen apps become unusable with 10k items without virtualization. This section will show you how to implement a virtualized list with react-window and optimize conditional rendering within items.
Common Anti-Patterns and How to Fix Them
Even experienced developers fall into traps with conditional rendering and lists. Common anti-patterns include: using index as key, mutating state directly (e.g., push instead of spread), rendering arrays of components without keys, using && with numbers, deeply nested ternaries, and mixing logic in JSX. Each of these leads to bugs or performance issues. For example, mutating state with push causes React to miss updates because the reference doesn't change. Always use immutable updates. Another anti-pattern is rendering a list inside a conditional without handling the empty state. This section will list the top 5 anti-patterns with before/after code, explaining why they're bad and how to fix them. Production code should be clean, predictable, and easy to debug.
Testing Conditional Rendering and Lists
Testing is crucial for production code. For conditional rendering, test each state (loading, empty, error, success) using React Testing Library. For lists, test that the correct number of items render, that keys are unique, and that interactions (like delete) work. Use data-testid or role queries to select elements. Avoid testing implementation details like state values; instead, test what the user sees. For example, assert that a loading spinner appears when the component is in loading state. Also, test edge cases: empty list, single item, and large lists. This section will provide a testing strategy with examples using Jest and React Testing Library. We'll also cover snapshot testing for list components, but caution against overusing it.
Real-World Patterns: Compound Components and Render Props
For complex conditional rendering, consider advanced patterns like compound components or render props. Compound components (e.g., <Tabs> with <Tab> and <TabPanel>) allow you to manage state internally while giving the consumer control over rendering. Render props let you pass a function as a child to control what gets rendered. These patterns are useful for building reusable UI libraries. For lists, consider using a generic List component that accepts a renderItem prop. This avoids duplicating list logic. In production, these patterns improve code reuse and separation of concerns. However, they can add complexity. Use them when you have multiple components sharing the same logic. This section will show a simple Tabs component using compound components and a List component with render props.
Conditional Rendering and Lists with TypeScript
TypeScript adds type safety to conditional rendering and lists. Define proper types for props and state to catch errors at compile time. For conditional rendering, use discriminated unions to model different states. For lists, ensure the key is typed correctly and that the data array is typed. Use generics for reusable list components. This section will show how to type a state machine with a discriminated union, and how to create a typed List component. In production, TypeScript prevents many runtime errors, especially when dealing with API responses that may have missing fields. Always use strict mode and avoid any. This will make your conditional rendering and list logic more robust.
Server-Side Rendering Considerations
When using SSR (Next.js, Remix), conditional rendering and lists behave differently. On the server, you don't have browser APIs like window or localStorage. Conditional rendering based on those will cause hydration mismatches. Use useEffect for client-only logic. For lists, ensure keys are stable across server and client to avoid hydration errors. Also, avoid rendering large lists on the server that could bloat the HTML payload. Consider lazy loading or client-side fetching for large datasets. This section will cover common SSR pitfalls and how to handle them, including using dynamic imports with ssr: false and using useEffect for client-side conditions. In production, hydration mismatches can break the entire page, so test thoroughly.
Conclusion: Build Production-Ready Conditional Rendering and Lists
Conditional rendering and lists are fundamental to React. By mastering these patterns—using early returns, stable keys, memoization, virtualization, and proper testing—you can build performant and maintainable UIs. Remember to handle all states, avoid anti-patterns, and leverage TypeScript for safety. In production, the difference between a good app and a great one often comes down to these details. Always think about edge cases: empty lists, loading states, errors, and large datasets. Use the patterns discussed here as a checklist for your next component. TheCodeForge.io encourages you to write code that is not just functional but production-ready from day one.
null vs undefined: When to Return Nothing
In React, returning null from a component means "render nothing," while returning undefined causes a runtime error in class components (though function components handle it gracefully by rendering nothing). The semantic difference matters: null explicitly signals an intentional empty state, whereas undefined often indicates a missing return statement. For reconciliation, React treats null as a signal to skip rendering and unmount any existing children, while undefined may lead to unexpected behavior in older React versions. Best practice: always return null for conditional rendering. Example: if (!items.length) return null; ensures the component renders nothing when the list is empty. Avoid returning undefined by ensuring all code paths have a return statement. In TypeScript, the return type should be JSX.Element | null to enforce this.
null checks to avoid rendering empty lists or loading states, improving performance by unmounting unnecessary components.null to render nothing; avoid undefined to prevent runtime errors and ensure clear intent.Form Conditional Patterns
Forms often require conditional logic, such as disabling the submit button when the form is invalid. Use state to track form validity and conditionally set the disabled attribute. Example: const [isValid, setIsValid] = useState(false); then <button disabled={!isValid}>Submit</button>. Update validity on input changes using validation functions. For complex forms, consider using a library like Formik or React Hook Form, but the core pattern remains: derive disabled state from validation state. Another pattern: show/hide form sections based on user selections (e.g., shipping address fields only if "different billing address" is checked). Use conditional rendering with && or ternary for these sections.
Complex Conditions: The Variable Assignment Pattern
When you have three or more mutually exclusive branches, using ternary operators or && chains becomes messy. The variable assignment pattern assigns JSX to a variable using let and conditionally sets it. Example: let content; if (status === 'loading') content = <Spinner />; else if (status === 'error') content = <Error />; else content = <Data />; return <div>{content}</div>;. This pattern is more readable and maintainable for complex logic. It also allows early returns for simple cases. For even more branches, consider using a lookup object or switch statement inside the component.
let variable assignment for complex conditional rendering with multiple branches to improve readability.| File | Command / Code | Purpose |
|---|---|---|
| ConditionalRenderingIntro.jsx | function UserGreeting({ user }) { | Why Conditional Rendering and Lists Are Core to React |
| ConditionalPatterns.jsx | function StatusMessage({ status }) { | Conditional Rendering Patterns |
| ListRendering.jsx | function TodoList({ todos }) { | Rendering Lists with map() and Keys |
| FilteredList.jsx | function ProductList({ products, searchTerm }) { | Conditional Rendering with Lists |
| MultiStateComponent.jsx | function DataFetcher({ url }) { | Handling Complex Conditional UI |
| VirtualizedList.jsx | const Row = React.memo(({ index, style, data }) => { | Performance Optimization |
| AntiPatterns.jsx | const addItem = () => { | Common Anti-Patterns and How to Fix Them |
| ListTest.test.js | test('renders empty state', () => { | Testing Conditional Rendering and Lists |
| CompoundList.jsx | function List({ items, renderItem }) { | Real-World Patterns |
| TypedList.tsx | type State | Conditional Rendering and Lists with TypeScript |
| SSRList.jsx | function ClientOnlyList() { | Server-Side Rendering Considerations |
| NullVsUndefined.jsx | function ItemList({ items }) { | null vs undefined |
| FormConditional.jsx | function SignupForm() { | Form Conditional Patterns |
| VariableAssignment.jsx | function Dashboard({ status }) { | Complex Conditions |
Key takeaways
Common mistakes to avoid
3 patternsNot understanding React component re-rendering
Ignoring the rules of hooks
Mutating state directly instead of using setState
Interview Questions on This Topic
What is the Virtual DOM and how does React use it?
Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
That's React. Mark it forged?
7 min read · try the examples if you haven't