12 min read
What Is React? A Friendly Introduction to the Whats and Whys
Hassib ModdasserSoftware engineerIf you’ve ever built an interactive page with plain JavaScript, you probably remember the moment it got messy. A button updates a counter. The counter changes a label somewhere else. That label decides whether a warning shows up. A few weeks later, nobody on the team is sure which piece of code changes what.
React was built to fix exactly that. In this article, I’ll walk you through what React is, how it thinks about building interfaces, and why it’s still my first choice for most frontend work. You don’t need any React experience, but you should be comfortable with basic HTML, CSS, and JavaScript.
So, what is React?
React is a JavaScript library for building user interfaces. It was created at Facebook (now Meta), released as open source in 2013, and today it’s maintained by Meta together with a huge community of developers.
That one sentence hides the most important idea, though. With React, you don’t write step-by-step instructions for changing the page. You describe what the page should look like for a given piece of data, and React takes care of updating the browser when that data changes.
Take a simple like counter. With plain JavaScript, you might write something like this:
const button = document.querySelector('#like')
const label = document.querySelector('#likes')
let likes = 0
button.addEventListener('click', () => {
likes++
label.textContent = `${likes} likes`
})It works, but you’re in charge of every single change to the page. Now look at the same idea in React:
function LikeButton() {
const [likes, setLikes] = useState(0)
return (
<button onClick={() => setLikes(likes + 1)}>
{likes} likes
</button>
)
}You never touch the DOM yourself. You change the data, likes, and the button updates itself. That shift in thinking, from “how do I change the page” to “what should the page look like right now,” is what React is really about.
Components: the building blocks
Everything in React is a component. A component is a JavaScript function that returns a piece of the interface. It can be as small as a button or as big as a whole page.
function Avatar() {
return <img className="avatar" src="/hassib.jpg" alt="Hassib" />
}The magic is that components can use other components, just like HTML tags:
function ProfileCard() {
return (
<div className="card">
<Avatar />
<h2>Hassib Moddasser</h2>
<p>Software engineer</p>
</div>
)
}Once you get used to this, you start seeing pages differently. A product page stops being one giant file and becomes a Header, a Gallery, a PriceTag, an AddToCartButton, and a ReviewList. Each piece is small enough to understand on its own, easy to test, and easy to reuse somewhere else.
Good to know
Component names always start with a capital letter. React treats <button> as a regular HTML tag and <Button> as one of your components.
JSX: HTML inside your JavaScript?
The HTML-looking code inside those functions is called JSX. It’s not actually HTML, and browsers can’t run it directly. Your build tool turns it into regular JavaScript function calls before it reaches the browser.
JSX looks strange at first, and plenty of developers (me included) found it odd to mix markup and logic in the same file. It grows on you quickly, because your markup and the logic that controls it finally live next to each other.
A few rules will save you some head-scratching:
- Use
classNameinstead ofclass, becauseclassis a reserved word in JavaScript. - Every tag must be closed, so
<img>becomes<img />. - A component returns one root element. If you don’t want an extra
div, wrap things in an empty fragment:<>...</>. - Curly braces let you drop any JavaScript expression into your markup.
function Greeting() {
const name = 'Sara'
const hour = new Date().getHours()
return (
<h1>
Good {hour < 12 ? 'morning' : 'afternoon'}, {name}!
</h1>
)
}Props: passing data down
Components get more useful when you can customize them. That’s what props are for. Props work like function arguments: the parent passes them in, and the child reads them.
function PriceTag({ amount, currency = 'USD' }) {
const price = new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)
return <span className="price">{price}</span>
}
function ProductCard() {
return (
<article>
<h3>Sourdough loaf</h3>
<PriceTag amount={6} />
<PriceTag amount={5.5} currency="EUR" />
</article>
)
}One important rule: props are read-only. A component should never change the props it receives. If something needs to change over time, that’s a job for state.
Data in React flows one way, from parent to child. It sounds limiting, but it’s one of the reasons React apps stay predictable as they grow. When something looks wrong on the screen, you follow the data upward until you find where it came from.
State: making things interactive
State is data that belongs to a component and changes over time. A form input’s value, whether a menu is open, the items in a shopping cart. You create state with the useState hook:
import { useState } from 'react'
function Menu() {
const [isOpen, setIsOpen] = useState(false)
return (
<nav>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Close menu' : 'Open menu'}
</button>
{isOpen && (
<ul>
<li>Home</li>
<li>Services</li>
<li>Contact</li>
</ul>
)}
</nav>
)
}useState gives you two things: the current value and a function to update it. When you call the update function, React runs your component again with the new value and updates whatever changed on the screen.
This next part trips up almost everyone at first. You never change state directly. This won’t work:
// Wrong: React doesn't know anything changed
isOpen = true
cart.push(newItem)Instead, you always give React a new value:
// Right: React sees a new value and re-renders
setIsOpen(true)
setCart([...cart, newItem])Arrays and objects are where people get caught, because push and direct assignments change the existing value instead of creating a new one. Spread syntax (...) is your friend here.
Handling events and forms
Events in React look a lot like HTML attributes, written in camelCase: onClick, onChange, onSubmit. You pass them a function, not a string.
Forms are where things get interesting. A classic pattern is the controlled input, where React state holds the value of the field:
function SearchBox() {
const [query, setQuery] = useState('')
return (
<>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search products"
/>
<p>You’re searching for: {query}</p>
</>
)
}React 19 also made forms a lot nicer. You can pass a function straight to a form’s action, and hooks like useActionState give you the pending state and the result without writing a pile of boilerplate:
function NewsletterForm() {
const [message, formAction, isPending] = useActionState(subscribe, '')
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={isPending}>
{isPending ? 'Subscribing...' : 'Subscribe'}
</button>
{message && <p>{message}</p>}
</form>
)
}Here subscribe is your own function that saves the email and returns a message to show. The contact form on this site works the same way, and it’s far less code than the old approach with useState, onSubmit, and event.preventDefault().
Showing lists and conditional content
Most real interfaces show lists: products, comments, search results. In React you use plain JavaScript map to turn an array of data into an array of elements:
const services = [
{ id: 1, name: 'Websites' },
{ id: 2, name: 'Web apps' },
{ id: 3, name: 'Management systems' },
]
function ServiceList() {
return (
<ul>
{services.map((service) => (
<li key={service.id}>{service.name}</li>
))}
</ul>
)
}Notice the key. React uses it to tell list items apart when the list changes, so it can add, remove, or reorder the right ones. Use a stable ID from your data. Using the array index works until items get reordered or deleted, and then you get some very confusing bugs.
For conditional content, you use regular JavaScript too. The && operator and the ternary ? : cover most cases, and an early return keeps bigger components readable:
function Cart({ items }) {
if (items.length === 0) {
return <p>Your cart is empty.</p>
}
return <p>You have {items.length} items in your cart.</p>
}How React updates the page
When state changes, React calls your component function again to get the new output. That’s called a render. Then it compares the new result with the previous one and updates only the parts of the real page that actually changed.
You may have heard this described as the “virtual DOM.” The name isn’t that important. What matters is the mental model: your components are like recipes. Given the same props and state, they should always produce the same result. Keep them pure like that and React can do its job well.
A render doesn’t mean the whole page is rebuilt. If only a price changes, only that piece of text changes in the browser. This is why React apps can stay fast even when the interface gets complex.
Good to know
The React Compiler, which became stable in 2025, can automatically skip work that doesn’t need to happen during a render. That means you need fewer manual optimizations like useMemo and useCallback than older tutorials suggest.
Effects, and when you don’t need them
Sometimes a component needs to talk to something outside React: a browser API, a timer, a third-party widget. That’s what useEffect is for. It runs after React has updated the page.
Say you want a modal to close when someone presses Escape. React doesn’t manage keyboard events on the whole window, so you set that up in an effect:
function Modal({ onClose, children }) {
useEffect(() => {
function handleKeyDown(event) {
if (event.key === 'Escape') onClose()
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [onClose])
return <div className="modal">{children}</div>
}The function you return is the cleanup. React runs it when the modal leaves the page, so you don’t leave event listeners hanging around.
My honest advice, though: reach for useEffect less than you think. A lot of beginner code uses effects to calculate values that could simply be calculated during render:
// You don't need an effect for this
const [fullName, setFullName] = useState('')
useEffect(() => {
setFullName(`${firstName} ${lastName}`)
}, [firstName, lastName])
// Just calculate it
const fullName = `${firstName} ${lastName}`The same goes for fetching data. You can fetch inside an effect, but in a real project you’ll usually get a better result from your framework’s data loading or a library like TanStack Query, which handles caching, loading states, and errors for you.
The hooks you’ll actually use
React has quite a few hooks, but day to day you’ll use a small handful:
| Hook | What it’s for |
|---|---|
useState | Data that changes and should update the screen |
useEffect | Syncing with things outside React, like timers or browser APIs |
useRef | Holding a value without re-rendering, or reaching a DOM element |
useContext | Reading shared data, like the current theme or logged-in user |
useActionState | Handling form submissions and their results |
You can also write your own hooks. A custom hook is just a function whose name starts with use and that calls other hooks. It’s the cleanest way to share logic between components. Remember the Escape key effect from earlier? It makes a perfect custom hook:
function useEscapeKey(onEscape) {
useEffect(() => {
function handleKeyDown(event) {
if (event.key === 'Escape') onEscape()
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [onEscape])
}Now any modal, dropdown, or popup can close on Escape with a single line: useEscapeKey(onClose). No copy-pasting the same setup into every component.
Library or framework?
React calls itself a library, and that’s accurate. React on its own handles the interface: components, state, and rendering. It doesn’t come with routing, data fetching, or a build setup.
In practice, though, most real projects use React through a framework that fills those gaps:
- Next.js is the most popular choice for websites and web apps. It adds routing, server rendering, and good SEO out of the box. This website is built with it.
- React Router can be used as a full framework too, and it’s a natural fit if you like its approach to routing and data loading.
- Vite is great when you want a plain single page app, like an internal dashboard that doesn’t need SEO.
- React Native lets you use the same component model to build iOS and Android apps.
If you find an older tutorial that starts with create-react-app, skip that part. It was officially deprecated in 2025, and the React team now recommends starting with a framework.
Why learn React?
There are plenty of good frontend tools out there. Vue, Svelte, and Angular are all solid, and I wouldn’t talk anyone out of them. Still, there are a few reasons React keeps winning for me and for a lot of teams.
The first is that the component model scales. The same ideas that build a small contact form also build a dashboard with hundreds of screens, so you never really outgrow them.
Then there’s the ecosystem. Whatever you need, whether that’s forms, charts, animations, maps, or accessible UI components, someone has probably built a well-maintained library for it already.
It’s also mostly just JavaScript. Loops are map, conditions are if and &&. The better you get at JavaScript, the better you get at React, and the other way around.
And it opens doors. React shows up in a huge share of frontend job listings, and companies like Meta, Netflix, Airbnb, and Shopify use it. Learning it also gets you most of the way to building mobile apps with React Native.
When React is overkill
React isn’t the answer to everything. If you’re building a simple landing page, a small brochure site, or a blog with almost no interactivity, plain HTML and CSS or a static site generator might be faster to build and lighter for your visitors.
My rule of thumb: the more your interface changes in response to what the user does, the more React pays off. Dashboards, booking systems, online stores, and anything with lots of forms are where it really shines.
How to start learning
If you’re starting today, here’s the path I’d recommend:
- Get comfortable with modern JavaScript first. Arrow functions, destructuring, spread syntax,
mapandfilter, modules, and promises. You’ll use all of them constantly. - Read the official docs at react.dev. They were completely rewritten a few years ago, and they’re one of the best learning resources I know of, with interactive examples on almost every page.
- Build something small but real. A to-do list is fine, but something you actually care about is better: a recipe box, a budget tracker, a page for a friend’s business.
- Then pick a framework. Once components, props, and state feel natural, move to Next.js and learn routing, data loading, and deployment.
Mistakes I see all the time
These come up again and again in code reviews, so it’s worth knowing them early:
- Changing state directly. Use
setItems([...items, newItem]), notitems.push(newItem). - Using effects for everything. If you can calculate a value from props or state, calculate it during render.
- Using array indexes as keys in lists that can change. Use real IDs.
- Giant components. If a component scrolls past your screen a few times, it probably wants to be split into smaller ones.
- Putting everything in global state. Most state belongs close to where it’s used. Start local and only lift it up when two components really need to share it.
Wrapping up
React changed how we build for the web by asking one simple question: given this data, what should the screen look like? Once that clicks, a lot of frontend work gets easier, more organized, and honestly more fun.
You don’t need to learn it all at once. Start with components, props, and state, build something small, and let the rest come as you need it.
If you have questions about React, or you’re planning a project and wondering whether it’s the right fit, feel free to reach out on LinkedIn or X. I’m always happy to talk shop.