React Elements and Rendering
React Elements and Rendering
Understanding how React creates and renders elements is fundamental to building React applications. Let's explore how React elements work and how they get rendered to the DOM.
React Elements
React elements are the smallest building blocks of React applications. They describe what you want to see on the screen.
Creating Elements
// Using JSX
const element = <h1>Hello, world!</h1>;
// Without JSX (what JSX compiles to)
const element = React.createElement(
'h1',
null,
'Hello, world!'
);
Element Structure
React elements are plain objects:
// The JSX <h1>Hello</h1> creates this object:
{
type: 'h1',
props: {
children: 'Hello'
}
}
Rendering Elements
To render a React element into the DOM, you need a "root" DOM node:
<!-- In your HTML file -->
<div id="root"></div>
// In your JavaScript file
import React from 'react';
import ReactDOM from 'react-dom/client';
const element = <h1>Hello, world!</h1>;
// Create a root
const root = ReactDOM.createRoot(
document.getElementById('root')
);
// Render the element
root.render(element);
Updating Rendered Elements
React elements are immutable. Once created, you can't change their children or attributes. To update the UI, create a new element and render it:
const root = ReactDOM.createRoot(
document.getElementById('root')
);
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);
}
setInterval(tick, 1000);
React Only Updates What's Necessary
React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring the DOM to the desired state.
// Even though we create a new element every second,
// React DOM only updates the time text node
function Clock() {
const [time, setTime] = React.useState(new Date());
React.useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div>
<h1>Static Title</h1>
<p>Current time: {time.toLocaleTimeString()}</p>
</div>
);
}
Component Elements
Elements can also represent user-defined components:
// Component definition
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// Component element
const element = <Welcome name="Sara" />;
// This creates:
{
type: Welcome,
props: {
name: 'Sara'
}
}
The Rendering Process
1. Initial Render
function App() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
2. Virtual DOM
React creates a virtual representation of the DOM:
// Virtual DOM representation
{
type: 'div',
props: {
children: [
{ type: Header, props: {} },
{ type: Content, props: {} },
{ type: Footer, props: {} }
]
}
}
3. Reconciliation
When state changes, React:
- Creates a new virtual DOM tree
- Compares it with the previous virtual DOM tree
- Calculates the minimum set of changes
- Updates only the changed parts in the real DOM
Conditional Rendering
Render different elements based on conditions:
function UserGreeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign up.</h1>;
}
// Using ternary operator
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<UserDashboard />
) : (
<LoginForm />
)}
</div>
);
}
Rendering Lists
Render multiple elements from arrays:
function NumberList({ numbers }) {
return (
<ul>
{numbers.map((number) => (
<li key={number.toString()}>
{number}
</li>
))}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
root.render(<NumberList numbers={numbers} />);
Keys in Lists
Keys help React identify which items have changed:
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
// Keys must be unique among siblings
const todos = [
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Build an app' },
{ id: 3, text: 'Deploy to production' }
];
Rendering Nothing
Components can return null
to render nothing:
function WarningBanner({ warn }) {
if (!warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
Portals
Render children into a DOM node outside the parent component:
function Modal({ children }) {
return ReactDOM.createPortal(
children,
document.getElementById('modal-root')
);
}
function App() {
return (
<div>
<h1>My App</h1>
<Modal>
<p>This is rendered outside the app root!</p>
</Modal>
</div>
);
}
Performance Considerations
1. Batching Updates
React batches multiple state updates for performance:
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// These updates are batched
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
// Results in one re-render, not three
};
return <button onClick={handleClick}>Count: {count}</button>;
}
2. React.memo
Prevent unnecessary re-renders:
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
return <div>{/* Complex rendering logic */}</div>;
});
3. Lazy Loading
Load components only when needed:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</React.Suspense>
);
}
Debugging Rendering
React DevTools
Use React DevTools to:
- Inspect component tree
- Track renders
- Profile performance
Console Logging
function MyComponent({ prop }) {
console.log('MyComponent rendered');
return <div>{prop}</div>;
}
Best Practices
- Keep components pure - Same props should always produce same output
- Avoid direct DOM manipulation - Let React handle the DOM
- Use keys properly - Stable, unique keys for list items
- Minimize re-renders - Use React.memo, useMemo, useCallback
- Batch state updates - Group related state changes
- Lazy load heavy components
- Profile performance - Use React DevTools Profiler
Common Pitfalls
1. Missing Keys
// ❌ Bad - using index as key
items.map((item, index) => <li key={index}>{item}</li>)
// ✅ Good - using stable, unique ID
items.map(item => <li key={item.id}>{item.name}</li>)
2. Modifying State Directly
// ❌ Bad
state.items.push(newItem);
// ✅ Good
setState([...state.items, newItem]);
3. Expensive Operations in Render
// ❌ Bad
function Component() {
const expensiveValue = calculateExpensiveValue(); // Runs every render
return <div>{expensiveValue}</div>;
}
// ✅ Good
function Component() {
const expensiveValue = useMemo(() => calculateExpensiveValue(), []);
return <div>{expensiveValue}</div>;
}
Next Steps
Now that you understand rendering in React, you're ready to learn about:
- Components and props
- State management
- Event handling
- Lifecycle methods and hooks