React.createElement() call that returns a plain JavaScript object describing what to render. This bridges the gap between JavaScript logic and UI structure, making components far more readable than writing raw createElement calls. JSX is not a requirement in React, but virtually all React projects use it because the template-like syntax closely mirrors the actual HTML output.
const element = <h1>Hello, world!</h1>;
ReactDOM.createRoot(root).render(element);
JSX is compiled by Babel into React.createElement() calls. It makes component templates more readable and expressive compared to writing raw JavaScript.
Why it matters: JSX is the language of React components. Every React developer writes it daily. Understanding that it compiles to function calls helps you debug errors and understand why certain things (like multiple root elements) are not allowed.
Real applications: Writing all React component templates, rendering dynamic lists and conditionals, passing event handlers and dynamic styles, and building reusable UI components with readable markup-style code.
Common mistakes: Returning multiple root elements without a Fragment wrapper (JSX must return one root), using class instead of className (reserved keyword in JS), and calling a function in onClick (onClick={fn()}) instead of passing the reference (onClick={fn}).
React.Component and must implement a render() method to return JSX. Since React 16.8 introduced hooks, functional components can manage state and side effects just as class components can, making class components largely unnecessary for new code. Most modern React codebases use functional components exclusively because they are shorter, simpler, and easier to test.
// Functional
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
// Class
class Greeting extends React.Component {
render() { return <h1>Hello {this.props.name}</h1>; }
}
Functional components are now preferred because they support hooks and are simpler to write and test.
Why it matters: Since React 16.8, functional components with hooks replaced class components as the standard. You will encounter both in codebases. Knowing the difference helps you read legacy code and write modern, clean components.
Real applications: All modern React apps use functional components for everything from buttons to full pages. Class components still appear in older codebases and are required if you use class-based error boundaries.
Common mistakes: Forgetting that class components use this.props and this.state instead of props/state directly, not knowing that hooks cannot be used inside class components, and writing new code using class components when functional components are available.
function UserCard({ name, age }) {
return <p>{name} is {age} years old</p>;
}
<UserCard name="Alice" age={30} />
Props are read-only — a component must never modify its own props. They flow one-way from parent to child.
Why it matters: Props are how components communicate. They make components reusable by letting the parent control what each instance displays. The one-way data flow rule keeps React apps predictable and easier to debug.
Real applications: Passing user data to a profile card, passing onClick handlers from parent to button components, sending API results down to display components, and building configurable reusable UI widgets like modals and dropdowns.
Common mistakes: Trying to modify props inside a component (they are read-only), passing too many props and creating prop drilling (use Context instead), and not destructuring props making code verbose (props.name vs name).
children prop. This keeps each component focused on one responsibility, making it easier to understand, test, and reuse independently. Large interfaces like dashboards or checkout flows are simply compositions of many well-defined smaller pieces.
function App() {
return (
<Layout>
<Header />
<MainContent />
<Footer />
</Layout>
);
}
This approach promotes reusability and separation of concerns. Each component manages its own responsibility, and they can be nested and combined to form the full UI.
Why it matters: Composition is the core design principle of React. Instead of inheritance, React uses composition to reuse code. Breaking UI into small components makes it easier to maintain, test, and understand.
Real applications: Building a page from a Header, Sidebar, and Content component, creating a Card component that accepts children, wrapping a Button with a Tooltip component, and composing form fields into a reusable Form component.
Common mistakes: Making components too large and doing too many things, not using the children prop to make components flexible, and repeating JSX in multiple places instead of extracting a shared component.
&&) operator to render something or nothing, and early returns with if statements for more complex branching. React renders nothing when an expression evaluates to null, undefined, or false, which makes show/hide logic clean and safe. You choose the pattern based on readability — ternary for two alternatives, && for simple show/hide, and early return for multiple conditions.
function Dashboard({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <UserPanel /> : <LoginForm />}
{isLoggedIn && <LogoutButton />}
</div>
);
}
Returning null from a component renders nothing. Choose the pattern that best communicates intent for each situation.
Why it matters: Almost every UI has elements that show or hide based on state. React gives you multiple clean ways to do this in JSX using plain JavaScript logic, which is one of the things that makes it powerful.
Real applications: Showing a loading spinner while data is fetching, hiding a menu until a button is clicked, displaying error messages only when errors exist, and showing user-specific content based on login status.
Common mistakes: Using {count && <Component />} when count can be 0 (renders "0" in the UI — use a ternary instead), adding too many nested ternaries making JSX unreadable, and not knowing that returning null is valid and safe.
Array.map(). Each rendered element requires a unique key prop so React's reconciliation algorithm can efficiently track which items were added, removed, or reordered. Without proper keys, React falls back to re-rendering the entire list on any change, which is slow and can cause bugs where input state or focus persists on the wrong item after a reorder. Keys should be stable, unique identifiers from your data — database record IDs are ideal; array indices work only when the list never changes order.
function TodoList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
Keys should be stable, unique identifiers — avoid using array indices as keys when the list can be reordered.
Why it matters: React uses keys to identify which items in a list changed, were added, or removed. Without keys (or with wrong ones), React makes incorrect DOM updates causing visual bugs or lost input state.
Real applications: Rendering a list of products, users, or messages from an API, showing a dynamic todo list, and rendering table rows from data. Any time you call .map() to render elements.
Common mistakes: Using array index as key when the list can change order (causes React to re-use the wrong component instance), not providing a key at all (React warns and diff is slow), and using random keys like Math.random() (creates a new key on every render, forcing full re-mount).
<div> wrappers would break the required HTML structure or interfere with CSS flex/grid layouts. Use the shorthand <>...</> for most cases and the full <React.Fragment key={...}> syntax when you need to assign a key prop.
function Columns() {
return (
<>
<td>Name</td>
<td>Age</td>
</>
);
}
Fragments are useful inside tables, definition lists, or anywhere extra wrapper divs would break the HTML structure.
Why it matters: React requires one root element per component. Without Fragments, developers add unnecessary wrapper <div> elements that pollute the DOM and can break CSS layouts like flexbox and grid.
Real applications: Returning multiple table cells (<td>) from a component, grouping sibling elements without a wrapper, returning adjacent JSX elements in a list render, and keeping the DOM clean.
Common mistakes: Adding a <div> wrapper just to satisfy the single root rule (use a Fragment instead), not knowing the short syntax <></> exists, and using short syntax <></> when you need to pass a key prop (must use <React.Fragment key={...}>).
React.createElement(type, props, ...children) call at build time. This function returns a plain JavaScript object called a React element, describing the component type, its props, and its children. React then uses these objects to build and diff the virtual DOM, applying only the minimal set of actual DOM changes needed. Understanding this compilation step explains JSX rules — like why you must use className instead of class and why component names must start with a capital letter.
// JSX
const el = <h1 className="title">Hello</h1>;
// Compiled
const el = React.createElement('h1', { className: 'title' }, 'Hello');
// Returns: { type: 'h1', props: { className: 'title', children: 'Hello' } }
Understanding this helps you reason about how JSX works and can be useful for dynamic element creation.
Why it matters: Knowing that JSX compiles to React.createElement() calls explains why JSX rules exist (single root, expressions only, className not class). It also lets you create elements dynamically when JSX syntax is not flexible enough.
Real applications: Building dynamic component factories that render different element types based on a prop, understanding how Babel transpiles your JSX, debugging unexpected JSX behavior, and working in environments where JSX is not available.
Common mistakes: Not knowing this connection and thinking JSX is some kind of magic, expecting to use HTML attribute names like class in JSX (it maps to className), and not knowing that <MyComponent /> requires MyComponent to start with a capital letter (lowercase means HTML tag).
React.PureComponent for class components and React.memo for functional components both add a shallow props comparison before rendering — if props haven't changed, the render is skipped. This optimization is especially effective for components that are expensive to render and often receive the same props.
// Class approach
class Card extends React.PureComponent {
render() { return <div>{this.props.title}</div>; }
}
// Functional equivalent
const Card = React.memo(function Card({ title }) {
return <div>{title}</div>;
});
This optimization prevents unnecessary renders and improves performance for components with stable props.
Why it matters: By default, a component re-renders every time its parent re-renders. React.memo and PureComponent prevent this by checking if props changed, which can significantly improve performance in large component trees.
Real applications: Wrapping expensive display components (charts, tables) with React.memo, using PureComponent in class-based lists, and optimizing components that receive the same data frequently but only need to re-render when something actually changed.
Common mistakes: Memoizing every component by default (adds overhead and is rarely needed for simple components), not pairing React.memo with useCallback for function props (functions are new references on each render, breaking memo), and using memo on components that almost always receive new props (no benefit).
defaultProps static property is still supported for backwards compatibility but is considered legacy for functional components and may be deprecated in a future React version. Default values also serve as living documentation, immediately communicating to other developers which props are optional and what the expected shape is.
// Default parameter (preferred)
function Button({ label = "Click me", color = "blue" }) {
return <button style={{ color }}>{label}</button>;
}
// defaultProps (legacy)
Button.defaultProps = { label: "Click me", color: "blue" };
Default parameter values are the modern standard. defaultProps is still supported but considered legacy for functional components.
Why it matters: Default props make components more resilient by handling missing or undefined props gracefully. They also serve as clear documentation of what a component expects and what values are optional.
Real applications: Button components with a default type="button", dropdown components with a default placeholder text, pagination components with default page size, and any reusable component where some props are optional.
Common mistakes: Using defaultProps in new functional component code (use default parameter values instead), not providing defaults for props that affect visual output (leads to blank UI instead of a fallback), and using prop || default instead of prop ?? default when 0 or false are valid values.
{}. This includes reading variables, calling functions, evaluating ternaries, doing arithmetic, and accessing object properties. The key restriction is that JSX curly braces only accept expressions (code that produces a value), not statements like if, for, or while — those must be converted to expressions or moved outside the JSX block. This constraint keeps JSX readable and predictable while giving you the full power of JavaScript for dynamic content.
function Greeting({ user, score }) {
const level = score > 100 ? 'Expert' : 'Beginner';
return (
<div>
<h1>Hello, {user.name.toUpperCase()}</h1>
<p>Level: {level}</p>
<p>Score doubled: {score * 2}</p>
</div>
);
}
You cannot use statements like if or for directly inside JSX curly braces — only expressions that resolve to a value.
Why it matters: JSX curly braces accept any JavaScript expression. This is why you can embed calculations, function calls, ternaries, and template literals directly in the markup, making React templates more dynamic than traditional HTML templates.
Real applications: Displaying computed values like formatted dates or prices, calling helper functions to format data inline, using ternaries for inline conditional text, and rendering dynamic class names or style values.
Common mistakes: Writing {if (x) return y} inside JSX (statements are not valid — use ternary), not knowing you can use IIFE {(() => { ... })()} for complex logic, and putting too much logic in JSX making it hard to read (extract to a variable above the return instead).
key prop to identify component instances across renders. When a key stays the same between renders, React updates the existing instance in place; when the key changes, React treats it as an entirely different component, destroys the old instance (clearing all its state, effects, and refs), and creates a brand new one. This makes key a powerful intentional reset tool — not just a performance hint for lists. It is the cleanest way to force a component back to its initial state when your data context changes entirely.
// Changing the key forces a full remount — resets all internal state
function App({ userId }) {
return <UserProfile key={userId} />;
}
// Without changing key:
// UserProfile re-renders but keeps its old state
// Changing key:
// UserProfile fully resets when userId changes
This is a clean technique to reset a component's state when a critical identity prop changes, without writing cleanup logic in useEffect.
Why it matters: Changing a key forces React to unmount the old component and mount a completely fresh one. This is a simple and official way to reset all local state and refs when an item's identity changes, which is a common need in real apps.
Real applications: Resetting a form when the user switches to editing a different record, refreshing a component when a user ID changes, clearing animation state when a tab changes, and reinitializing third-party libraries inside a component after a major prop change.
Common mistakes: Using useEffect to manually reset all state fields when a key change would be simpler, not knowing this technique at all and writing complex reset logic, and using this technique too frequently causing unnecessary unmount/mount cycles.
SyntheticEvent object that provides a consistent API regardless of browser differences. Event handler names in JSX are written in camelCase (onClick, onChange, onSubmit) and accept a function reference — not a string like in HTML attributes. The SyntheticEvent exposes all the familiar methods like preventDefault() and stopPropagation() and properties like target.value. React attaches a single delegated event listener at the root rather than on each element, making event handling efficient even in large component trees.
function ClickDemo() {
const handleClick = (e) => {
e.preventDefault();
console.log('Button clicked!');
};
return <button onClick={handleClick}>Click me</button>;
}
// Inline arrow function — pass args easily
<button onClick={() => handleDelete(item.id)}>Delete</button>
Pass the function reference — do not call it: write onClick={handleClick}, never onClick={handleClick()}.
Why it matters: React uses its own synthetic event system that normalizes browser differences. Knowing how to attach handlers, pass arguments, and prevent default behavior correctly is essential for building interactive UIs.
Real applications: Handling button clicks, form submissions, keyboard shortcuts, hover effects, and drag events. Every interactive React component uses event handlers.
Common mistakes: Calling the function immediately (onClick={fn()}) instead of passing the reference, forgetting to call e.preventDefault() on form submissions, and not knowing that React's onChange fires on every keystroke (unlike HTML's change event which fires on blur).
null, React renders absolutely nothing to the DOM for that component, making it visually absent from the page. This is intentionally different from unmounting — the component instance remains in the tree, its hooks continue to execute, and any active effects or subscriptions remain live. This pattern is useful when you need to conditionally hide content while keeping its state and effects intact, such as hiding a notification panel without losing its unread count. Returning null is cleaner than using CSS to hide elements for components that have meaningful side effects you want to keep running.
function WarningBanner({ show, message }) {
if (!show) return null; // renders nothing, component stays mounted
return (
<div className="warning">{message}</div>
);
}
<WarningBanner show={hasError} message="Something went wrong" />
This is the standard pattern to conditionally hide a component without removing it from the component tree entirely.
Why it matters: Returning null is the proper way to render nothing in React. It skips the element from the DOM without errors and keeps the component in the tree so it still participates in reconciliation (though nothing is rendered).
Real applications: A notification badge that only renders when there are unread messages, a tooltip that renders only when hovered, a dialog component that renders its content only when open, and loading skeletons that hide when data arrives.
Common mistakes: Returning undefined instead of null (React throws an error), using CSS display: none when removing from the DOM is better, and not knowing that returning null still triggers lifecycle methods and effects (the component is still mounted).
className attribute connects components to traditional CSS stylesheets, and CSS Modules extend this with locally-scoped class names that prevent naming collisions across components. CSS-in-JS libraries like styled-components or Emotion take a component-driven approach where styles live directly alongside the component they belong to.
// Inline styles — camelCase property names, no hyphen
<div style={{ backgroundColor: 'blue', fontSize: '16px' }}>Box</div>
// className — same as HTML class, supports dynamic values
<button className={`btn ${isActive ? 'btn-active' : ''}`}>Go</button>
// CSS Modules — scoped, avoids class name collisions
import styles from './Card.module.css';
<div className={styles.card}>Content</div>
CSS Modules are great for component-scoped styles. Tailwind CSS is widely used for utility-first styling.
Why it matters: React gives you multiple ways to style components. Knowing which approach to use in which context prevents style bleed, specificity wars, and maintainability issues in large applications.
Real applications: Using CSS Modules for scoped component styles, Tailwind for rapid UI development, styled-components for dynamic styles based on props, and inline styles for one-off adjustments or programmatically computed values.
Common mistakes: Using class instead of className in JSX, writing inline styles as strings instead of objects (style="color:red" is invalid — use style={{color: 'red'}}), and mixing multiple styling approaches inconsistently in the same project.
React.memo adds a shallow comparison of props between renders: if all props are equal by reference, the previous render output is reused and the component function is not called again. This optimization is most effective for components that are expensive to render and frequently receive the same props from a parent that updates often. For React.memo to work fully, object and function props must also be stabilized using useMemo and useCallback in the parent.
// Without memo — re-renders whenever parent re-renders
function Avatar({ name, url }) {
return <img src={url} alt={name} />;
}
// With memo — skips re-render if name and url are unchanged
const Avatar = React.memo(function Avatar({ name, url }) {
return <img src={url} alt={name} />;
});
// Also stabilize callback props with useCallback
const handleClick = useCallback(() => doSomething(id), [id]);
React.memo only helps when the parent re-renders frequently and the child's props are stable. Always pair it with useCallback for function props.
Why it matters: React re-renders components by default on every parent render. Knowing when and how to prevent unnecessary re-renders is a key performance skill that becomes important as apps grow in complexity.
Real applications: Wrapping list items with React.memo to prevent full list re-renders on parent state changes, combining useCallback on handlers passed to memoized children, and using the DevTools Profiler to identify components that render too often.
Common mistakes: Applying React.memo everywhere without measuring (premature optimization), not providing the custom comparison function when shallow comparison is not enough, and forgetting that object/array/function props are new references on every render (breaking the memo unless wrapped with useMemo/useCallback).