React didn't win because it was the first component-based framework — it won because it made one idea click: your UI is a function of your state. Change the state, and React figures out what actually needs to redraw. You stop manually updating the DOM and start describing what things should look like.
The fastest path today is Vite, not Create React App (which is no longer the recommended default):
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
That gives you a dev server with hot reload in under a minute.
A component takes props in and returns JSX out. That's the whole model:
function ArticleCard({ title, summary }) {
return (
<div className="card">
<h3>{title}</h3>
<p>{summary}</p>
</div>
);
}
JSX looks like HTML but compiles to regular JavaScript function calls — React.createElement under the hood. That's why you can drop {expressions} anywhere: it's just JavaScript.
Components re-render when their state changes. useState is how you declare state that React tracks:
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
The rule that trips people up: never mutate state directly (count++). Always call the setter. React compares the old and new values to decide whether to re-render, and mutating in place breaks that comparison.
Fetching data, subscribing to something, manually touching the DOM — anything that isn't "compute the UI from props and state" is a side effect, and belongs in useEffect:
useEffect(() => {
fetch('/api/articles')
.then(res => res.json())
.then(setArticles);
}, []); // empty array = run once, on mount
The dependency array is the part people get wrong most often. It's not optional — it's how React knows when to re-run the effect. Leave out a value you actually use inside the effect, and you'll be reading stale data.
{articles.map(a => (
<ArticleCard key={a.id} title={a.title} summary={a.summary} />
))}
key isn't a prop your component receives — React uses it internally to track which item is which across re-renders. Use a stable, unique ID. Using the array index works until items get reordered or deleted, then it silently causes bugs (wrong item gets removed, form inputs lose focus on the wrong row).
Once components, state, and effects feel natural, the next real jump is state that needs to be shared across components that aren't directly related — that's when people reach for Context, or a library like Zustand or Redux. Don't reach for either before you actually feel the pain of prop-drilling three levels deep; most apps never need it.
Comments