You already know JavaScript, but when you open a .jsx file it feels like a foreign language. The component doesn't update, props don't arrive, state doesn't behave as expected. You're not the first, and it's not your fault. React has a different logic than jQuery or vanilla JS. We at Meteora Web have built React apps for years — proprietary platforms, dashboards, e-commerce — and we've seen hundreds of developers (including ourselves) trip over the same spots. This guide gives you the foundation to stop fighting React and start using it like a pro.
JSX: It's Not HTML, It's Syntactic Sugar
JSX looks like HTML, but it isn't. Babel compiles it into React.createElement calls. That means every HTML attribute has a different name in JSX: class becomes className, for becomes htmlFor, tabindex becomes tabIndex, and so on. Inline styles aren't strings but objects with camelCase properties.
// Wrong
<div class="container" style="color: red;">
Hello
</div>
// Correct
<div className="container" style={{ color: 'red' }}>
Hello
</div>
Common mistake: writing native HTML attributes and wondering why it doesn't work. JSX is not a template engine — it's JavaScript that produces React elements. Every expression inside curly braces {} is evaluated as JavaScript. That's why you can put functions, ternaries, array.map, but not if or for (use them outside the return or with expressions).
What to do now: Open an empty component and write a div with className and a style object. Then try inserting a variable inside {}. If the text color changes, you've got the mechanism.
Components: Functions, Not Classes
In 2025 class components are legacy. There's no reason to use them anymore: Hooks (since React 16.8) cover everything. A React component is a function that receives props and returns JSX. Period.
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
As you see, the component is pure: data in (props), JSX out. No this, no render(), no prehistoric state manager. Less is more. You can export it as default or named, depending on the project's needs.
Common mistake: forgetting implicit return with arrow functions, or closing tags that should be self-closing (e.g. <img />). React requires valid JSX, otherwise it throws.
What to do now: Transform a class component you wrote in the past into a function. If you don't have one, create a Welcome that takes a name prop and prints "Hello, {name}!"
Props: The One-Way Data Flow
Props are function parameters: they come from outside and must not be mutated. A child component should never reassign props.name. Props are read-only. This is the principle of unidirectional data flow: data flows down from parent to child.
function Avatar({ src, alt, size = 50 }) {
return <img src={src} alt={alt} width={size} height={size} />;
}
// Usage
<Avatar src="https://example.com/me.jpg" alt="My photo" />
Notice: size has a default value (50). This is the modern way to handle optional values, replacing deprecated defaultProps. If you use TypeScript, define an interface instead.
Common mistake: mutating props in the child. If you need derived local data, use useState or useMemo. Don't write props.size = 100 — React won't allow it and will warn you in the console.
What to do now: Create a parent component that passes an array of objects to a child, and the child renders them in a list with .map() and a key. Use children to pass arbitrary markup.
State: The Component's Local Memory
State is the fundamental difference: while props come from outside and are immutable, state is managed internally and can change. The useState hook returns an array with two items: the current value and an update function.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
+1
</button>
</div>
);
}
Attention: setCount is asynchronous. If you need the previous value to compute the next one, use the functional form: setCount(prev => prev + 1). This prevents bugs when state updates happen in rapid succession.
Common mistake: putting everything into one state, or over-fragmenting it. Rule of thumb: if a value can be derived from props or other state, don't store it in state. Calculate it on the fly with useMemo. If two sibling components share the same state, lift it up to the common parent (lifting state up).
What to do now: Add a "-1" button to the counter and use the functional form to ensure it works even with fast clicks. Then create a form with two controlled fields (each with its own useState) and a submit button.
Re-renders and Optimization
React re-renders a component whenever its props or state change. By default, the entire descendant tree re-renders. This is the intended behavior most of the time, but in heavy components it can cause slowdowns.
Solutions:
- React.memo: wrap the component to avoid re-renders if props haven't changed (shallow comparison).
- useMemo: memoize the result of an expensive computation until dependencies change.
- useCallback: avoid recreating functions on every render, useful when passed as props to memoized components.
const MemoizedList = React.memo(function List({ items }) {
console.log('Rendering list');
return items.map(item => <p key={item.id}>{item.name}</p>);
});
When to use these optimizations? Only when you measure a real problem. Don't over-optimize — React is already fast. We've seen it on dozens of projects: profile with React DevTools first, then intervene.
TypeScript + React: The Winning Combo
If you're not using TypeScript yet, now is the time. Typing props and state saves you hours of debugging. With a .tsx file you can define clear interfaces:
interface CardProps {
title: string;
children: React.ReactNode;
onClose?: () => void; // optional
}
function Card({ title, children, onClose }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{onClose && <button onClick={onClose}>X</button>}
<div>{children}</div>
</div>
);
}
This way, if you pass a wrong prop or forget a required one, TypeScript warns you at compile time, not runtime. We at Meteora Web use TypeScript in every React project. The initial cost is tiny, the payoff is massive.
What to do now: Install TypeScript in an existing React project (or create a new one with Vite: npm create vite@latest and choose React+TS). Convert one of the components you wrote by adding interfaces for props and state.
Summary — What To Do Next
- Stop thinking of JSX as HTML. Every attribute has a JavaScript name, and expressions go inside
{}. - Always use function components and hooks. Classes are history. Learn
useStateanduseEffectand you're covered for 80% of cases. - Props flow down, state stays local. Never mutate props. Lift state up when needed by multiple components.
- Type with TypeScript. Don't postpone: every hour spent on a type bug is wasted time.
- Install React DevTools. They show you state, props, and re-renders in real time.
You now have the foundation to build solid, maintainable React applications. We used these same concepts for platforms like an automated social media publishing system and analytics dashboards — and they work. To dive deeper, check out how we integrate React with Laravel backends for full-stack apps. The rest is practice.
Sponsored Protocol