React Fundamentals Every Next.js Developer Must Know
Next.js is built entirely on top of React, which means every Next.js page, layout, and component you write is, underneath, a React component. If your React fundamentals are shaky, Next.js concepts like Server Components, data fetching, and routing will feel confusing not because they're inherently difficult, but because they're layered on top of React ideas you haven't fully internalized yet.
This lesson is a focused refresher on the five React concepts you will use constantly throughout this course: JSX, components, props, state, and hooks. If you're already comfortable with React, treat this as a quick check to confirm your mental model is solid before moving into Next.js-specific territory.
Learning Objectives
- Understand JSX and how it differs from HTML.
- Explain what a React component is and how components compose together.
- Differentiate between props and state and know when to use each.
- Use the useState hook to manage local component state.
- Use the useEffect hook to run side effects in response to changes.
Core Definitions
- JSX: A syntax extension for JavaScript that lets you write HTML-like markup directly inside JavaScript/TypeScript code, which gets compiled into React function calls.
- Component: A reusable, self-contained function that returns JSX describing a piece of UI.
- Props: Short for 'properties'; data passed from a parent component into a child component, read-only from the child's perspective.
- State: Data that a component manages internally and that can change over time, triggering a re-render when updated.
- Hook: A special React function (like useState or useEffect) that lets function components use features like state and side effects.
- useState: A hook that adds local state to a function component, returning the current value and a setter function.
- useEffect: A hook that runs side effects (like data fetching or subscriptions) in response to component rendering or dependency changes.
Detailed Explanation
JSX looks like HTML but it isn't HTML — it's syntactic sugar that compiles down to JavaScript function calls (React.createElement under the hood). This is why JSX allows embedding JavaScript expressions directly using curly braces, like {user.name}, and why attributes use camelCase (className instead of class, onClick instead of onclick) since they map to JavaScript property names rather than HTML attribute names.
A component is simply a JavaScript function that returns JSX. Function name capitalized, like Button or ProductCard, and React treats it as a reusable UI building block. Components compose: a Page component might render a Navbar component and a ProductList component, which itself renders many ProductCard components — this nesting is the entire mental model of building UI in React.
Props flow data downward, from parent to child, and are read-only within the child. If a ProductCard component receives a prop called price, it can display that price but cannot modify it directly — if the price needs to change, that change must happen in the parent that owns the data.
State is different: it's data a component owns and can change itself, using the useState hook. Calling `const [count, setCount] = useState(0)` gives you a current value (count) and a function to update it (setCount). Calling setCount schedules a re-render with the new value — this is the mechanism that makes React UIs interactive.
useEffect handles side effects — things that happen outside the normal render-and-return flow, like fetching data from an API, setting up an event listener, or synchronizing with a browser API. It runs after render, and its dependency array controls exactly when it re-runs: an empty array [] means 'run once, after the first render'; including a variable means 'run again whenever that variable changes.'
In Next.js's App Router specifically, these hooks (useState, useEffect) can only be used inside Client Components, marked with a 'use client' directive at the top of the file, since interactive state and browser-only effects don't make sense in server-rendered code — a distinction later lessons will cover in depth.
Diagram Description
Visualize a component tree with data flow arrows:
[App Component]
/ | \
[Navbar] [ProductList] [Footer]
|
(maps over products array)
|
[ProductCard] × N
Props flow DOWN: App --price=499--> ProductList --price=499--> ProductCard
State lives LOCALLY: ProductCard manages its own 'isFavorited' state internally via useState
Comparison Table
| Concept | Owned By | Can Change? | Typical Use |
|---|---|---|---|
| Props | Parent component | No (read-only in child) | Passing data/configuration down to children |
| State | The component itself | Yes, via setter function | Tracking values that change over time (form input, toggles) |
| JSX | N/A (syntax) | N/A | Describing UI structure inside JS/TS |
| useState | Function component | Triggers re-render on change | Local, interactive component data |
| useEffect | Function component | Runs after render | Side effects: fetching data, subscriptions, timers |
Next.js Practical Example
'use client'; // Required in Next.js App Router for hooks like useState/useEffect
import { useState, useEffect } from 'react';
// A reusable component receiving 'label' via props
function Counter({ label }: { label: string }) {
// Local state: count starts at 0
const [count, setCount] = useState(0);
// Side effect: runs whenever 'count' changes
useEffect(() => {
document.title = `${label}: ${count}`;
}, [count, label]);
return (
<div>
<p>{label}: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default function CounterPage() {
return (
<main>
<Counter label="Clicks" />
</main>
);
}
CounterPage renders the Counter component and passes it a prop called label with the value "Clicks" — Counter cannot change this value itself, only display it. Inside Counter, useState creates local state (count) that the component owns and can update via setCount whenever the button is clicked, triggering a re-render with the new count. The useEffect hook runs after every render where count or label has changed, updating the browser tab's title — a classic 'side effect' that happens outside the normal JSX-returning render flow. The 'use client' directive at the top is required in the Next.js App Router because useState and useEffect rely on browser interactivity and cannot run in a Server Component.
Industry Examples
- E-commerce 'Add to Cart' buttons use useState to track a loading or 'added' state locally within the button component before the cart updates globally.
- Dashboard applications use useEffect to fetch fresh data when a filter or date range prop changes, keeping displayed data in sync with user selections.
- Form components across nearly every production React and Next.js app use useState to track each input's current value as the user types.
- Component libraries like design systems (e.g., internal button, modal, and card components) are built entirely around the props pattern, letting teams reuse one component with different data and configuration everywhere.
- Real-time notification badges use useState combined with useEffect (often alongside WebSockets or polling) to update an unread count as new events arrive.
Interview Questions and Answers
Q1. What is the difference between props and state in React?
Props are data passed from a parent component to a child component and are read-only from the child's perspective — the child cannot modify them directly. State is data that a component manages internally using a hook like useState, and the component itself can update it, which triggers a re-render.
Q2. What is JSX and why can't browsers run it directly?
JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. Browsers cannot execute JSX directly; it must be compiled (typically by a tool like Babel or the Next.js/SWC compiler) into plain JavaScript function calls, such as React.createElement, before it can run.
Q3. What does the useState hook return and how is it typically used?
useState returns an array with two elements: the current state value and a setter function to update it. It's typically used with array destructuring, like `const [count, setCount] = useState(0)`, where calling setCount(newValue) updates the state and triggers a re-render.
Q4. When does useEffect run and how does the dependency array control that?
useEffect runs after the component renders. The dependency array determines when it re-runs: an empty array [] means it runs only once after the initial render; omitting the array means it runs after every render; including specific variables means it re-runs whenever any of those variables change.
Q5. Why are hooks like useState only usable in Client Components in the Next.js App Router?
Hooks like useState and useEffect rely on interactivity and browser APIs that don't exist during server rendering. The App Router requires marking a file with the 'use client' directive to opt into client-side rendering and hook usage; Server Components, which render only on the server, cannot use these hooks.
MCQs With Answers
1. What is JSX?
- A CSS preprocessor
- A syntax extension that lets you write HTML-like markup in JavaScript
- A database query language
- A testing framework
Answer: B. A syntax extension that lets you write HTML-like markup in JavaScript
Explanation: JSX lets developers write HTML-like syntax directly inside JavaScript/TypeScript, which is then compiled into JavaScript function calls React understands.
2. Which statement correctly describes props?
- Props can be modified by the child component
- Props are read-only data passed from parent to child
- Props are only used for styling
- Props replace the need for state entirely
Answer: B. Props are read-only data passed from parent to child
Explanation: Props flow one-directionally from parent to child and are read-only within the receiving child component.
3. What does calling the setter function returned by useState do?
- Deletes the component
- Updates the state value and triggers a re-render
- Runs a side effect immediately
- Changes a prop's value
Answer: B. Updates the state value and triggers a re-render
Explanation: Calling the setter function (e.g., setCount) updates the stored state value and schedules React to re-render the component with the new value.
4. An empty dependency array [] passed to useEffect means:
- The effect never runs
- The effect runs after every render
- The effect runs only once, after the initial render
- The effect runs before the component mounts
Answer: C. The effect runs only once, after the initial render
Explanation: An empty dependency array tells React the effect has no dependencies to watch, so it runs a single time after the component's first render.
5. In the Next.js App Router, which directive is required to use hooks like useState?
- 'use server'
- 'use client'
- 'use effect'
- No directive is needed
Answer: B. 'use client'
Explanation: The 'use client' directive marks a file as a Client Component, which is required to use interactive hooks like useState and useEffect since Server Components cannot use them.
Common Mistakes to Avoid
- Trying to modify a prop directly inside a child component instead of lifting the change up to the parent that owns the data.
- Forgetting the dependency array in useEffect, causing the effect to re-run after every single render unintentionally.
- Using useState or useEffect inside a Next.js Server Component without adding the 'use client' directive, causing an error.
- Confusing state updates as synchronous; setCount does not update count immediately within the same function call — it schedules a re-render.
- Mutating state directly (e.g., pushing to an array stored in state) instead of using the setter function with a new array/object reference.
Interview Notes
- JSX compiles to JavaScript function calls; it is not valid HTML and cannot run directly in browsers without compilation.
- Props flow one-way, parent to child, and are read-only in the receiving component.
- State is owned by the component and updated via a setter function returned from useState, triggering re-renders.
- useEffect's dependency array controls when the effect re-runs: [] = once, [x] = whenever x changes, omitted = every render.
- In the Next.js App Router, hooks like useState/useEffect require the 'use client' directive since they need browser interactivity.
Key Takeaways
- Every Next.js file that returns UI is, at its core, a React component — so React fundamentals directly underpin all Next.js work.
- Props and state solve different problems: props configure a component from the outside; state tracks what changes from the inside.
- useState and useEffect are the two hooks you'll reach for constantly to build interactive, data-driven UI.
- The Client/Server Component distinction in Next.js is built directly on top of which React features (like hooks) a given file needs.
Summary
React fundamentals form the bedrock of everything you'll build in Next.js. JSX lets you describe UI using HTML-like syntax that compiles into JavaScript. Components are reusable functions returning JSX, composed together to build full interfaces. Props pass read-only data from parent to child, while state is data a component owns and updates itself using the useState hook, triggering re-renders. useEffect handles side effects like data fetching, running based on a dependency array that controls exactly when it re-executes. In the Next.js App Router, these interactive hooks require the 'use client' directive, connecting this React refresher directly to Next.js-specific concepts covered in later lessons.