React is a JavaScript library used to build dynamic, interactive user interfaces. Its core concepts revolve around components, state management, and efficient rendering. Here’s a quick overview of the key concepts:
🎨 1. JSX: Syntax Similar to HTML
JSX (JavaScript XML) allows you to write code resembling HTML within JavaScript.
const App = () => {
return <h1>Welcome to my app!</h1>;
};
✅ JSX must be wrapped in a single parent element.
📦 2. Components: Building Blocks of React
🔹 Functional Component
A functional component is simply a function that returns JSX.
const Message = ({ name }) => <h1>Hello, {name}!</h1>;
✅ Components allow for reusability and modularity.
🎛 3. Props: Passing Data to Components
Props are used to pass data to child components.
const Welcome = ({ user }) => <h2>Welcome, {user}!</h2>;
const App = () => <Welcome user="Alice" />;
✅ Props are immutable (cannot be changed by the child component).
⚡ 4. State (useState
): Dynamic Data
State allows you to store and update dynamic data.
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
✅ useState(initialValue)
returns a state value and an update function.
🔄 5. Side Effects (useEffect)
The useEffect hook lets you perform side effects in functional components - things like fetching data, setting timers, or manually interacting with the DOM.
import { useState, useEffect } from 'react';
const Timer = () => {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(interval); // Cleanup
}, []);
return <p>Time elapsed: {seconds} sec</p>;
};
✅ Common use cases: API calls, timers, subscriptions, event listeners, analytics, cleanup logic, etc.
📦 Behavior of useEffect Based on Dependencies
✅ useEffect(() => { ... }, [])
Runs only once after the component is mounted (like componentDidMount in class components).
Useful for:
-
Fetching data when the component loads
-
Setting up subscriptions or timers
-
Initializing libraries
🔁 useEffect(() => { ... }) (no dependency array)
Runs after every render, including initial mount and every update.
⚠️ Use with caution - can lead to performance issues or infinite loops if the effect updates state.
Useful for:
-
Logging all renders
-
Debugging or testing re-render behavior
🎯 useEffect(() => { ... }, [dep1, dep2])
Runs after the component mounts and every time one of the listed dependencies changes.
Example:
useEffect(() => {
console.log(`User changed to ${userId}`);
fetchUserData(userId);
}, [userId]);
🧹 Cleanup Function
useEffect can return a cleanup function that runs:
-
When the component unmounts
-
Or before the effect re-runs (when dependencies change)
Example:
useEffect(() => {
const handleScroll = () => console.log('scrolling...');
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll); // Cleanup
};
}, []);
🎭 6. Conditional Rendering
React allows conditional rendering based on state.
const UserStatus = ({ isLoggedIn }) =>
isLoggedIn ? <p>Welcome, user!</p> : <p>Please log in.</p>;
✅ Allows for showing or hiding elements based on state.
📜 7. Lists and Keys (.map()
)
Lists are dynamically rendered using .map()
.
const fruits = ['Apple', 'Banana', 'Orange'];
const FruitList = () => (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
✅ Each element must have a unique key (key
).
🌍 8. React Router: Navigation with react-router-dom
react-router-dom
enables navigation without page reload.
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
const Home = () => <h2>Home</h2>;
const About = () => <h2>About</h2>;
const App = () => (
<Router>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
export default App;
✅ Use Routes
and Route
to define paths.
✅ Use Link
instead of <a>
to prevent reloading.
🏷 9. Dynamic Parameters (useParams
)
Allows rendering dynamic content based on URL.
import { useParams } from 'react-router-dom';
const UserProfile = () => {
const { id } = useParams();
return <h2>User Profile {id}</h2>;
};
const App = () => (
<Router>
<Routes>
<Route path="/user/:id" element={<UserProfile />} />
</Routes>
</Router>
);
✅ useParams()
extracts parameters from the URL (/user/123
).
🔄 10. Programmatic Navigation (useNavigate
)
Allows navigation programmatically.
import { useNavigate } from 'react-router-dom';
const Dashboard = () => {
const navigate = useNavigate();
return <button onClick={() => navigate('/')}>Back to Home</button>;
};
✅ Replaces history.push()
from older React Router versions.
🌍 11. useContext: Global State Management
Context API allows for sharing global state without passing props. Useful for themes, languages, or authenticated users.
🏗 Setting up context
1️⃣ Create the context
const ThemeContext = createContext();
2️⃣ Provide a value in context
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
3️⃣ Use context in a component
const ThemedComponent = () => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div className={theme}>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Change theme
</button>
</div>
);
};
✅ Advantage: Avoids "prop drilling" (unnecessary passing of props through multiple levels).
🔼 12. Lifting State Up
When multiple components need the same data, store the state in a common parent and pass it as props to children.
🎯 Example: A Shared Counter
💡 Bad approach: Each component manages its own state.
const CounterA = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>A: {count}</button>;
};
const CounterB = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>B: {count}</button>;
};
💡 Good approach: State is centralized in a parent.
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<Counter count={count} increment={() => setCount(count + 1)} />
<Counter count={count} increment={() => setCount(count + 1)} />
</>
);
};
const Counter = ({ count, increment }) => (
<button onClick={increment}>Counter: {count}</button>
);
✅ Advantage: Synchronizes data between multiple components.
🔄 13. useReducer: Complex State Management
useReducer
is an alternative to useState
, useful for managing state with multiple actions.
📌 Example: A Counter with Multiple Actions
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>Value: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
};
✅ Advantage: Easier to manage than useState
with multiple nested updates.
⏳ 14. useMemo & useCallback: Performance Optimization
These hooks avoid unnecessary calculations and re-renders.
⚡ useMemo: Memoize Computation (a value)
const computeExpensiveValue = (num) => {
console.log('Calculating...');
return num * 2;
};
const App = () => {
const [count, setCount] = useState(0);
const computedValue = useMemo(() => computeExpensiveValue(count), [count]);
return <p>Result: {computedValue}</p>;
};
✅ Prevents recalculating computeExpensiveValue()
if count
hasn't changed.
🎯 useCallback: Memoize Functions
Memoizes a callback function between renders.
const increment = useCallback(() => setCount(count + 1), [count]);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
✅ Prevents recreating a function on every render (unnecessary child re-renders).
🔁 15. useRef: Persistent Mutable Values
useRef lets you persist values between renders without triggering a re-render.
const Timer = () => {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => console.log('Tick'), 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <p>Timer running (check console)</p>;
};
✅ Great for timers, DOM access, or tracking previous values.
🎭 16. Portals: Render Outside Parent DOM
Portals allow rendering a component outside the React root div
.
const Modal = ({ onClose }) => {
return ReactDOM.createPortal(
<div className="modal">
<p>I am a Modal!</p>
<button onClick={onClose}>Close</button>
</div>,
document.getElementById('modal-root')
);
};
✅ Useful for modals, tooltips, or notifications.
🎣 17. Custom Hooks: Reusing Logic
Custom Hooks allow you to reuse logic across components (extract reusable logic into a custom hook).
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return width;
}
📌 Example: A Counter Hook
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
return { count, increment: () => setCount(count + 1) };
};
const App = () => {
const { count, increment } = useCounter();
return <button onClick={increment}>Counter: {count}</button>;
};
✅ Improves code reusability and readability.
🚦 18. Suspense & Lazy Loading
React allows you to load components only when necessary.
⚡ Lazy Load a Component
const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => (
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>
);
✅ Improves performance and reduces initial load time.
🔀 19. Fragment (<></>
)
Allows returning multiple elements without adding unnecessary HTML wrappers.
const App = () => (
<>
<h1>Title</h1>
<p>Description</p>
</>
);
🎨 20. Styling: CSS-in-JS, Tailwind, etc.
React is styling-agnostic. You can use:
-
Traditional CSS
-
CSS Modules
-
Tailwind CSS
-
styled-components (CSS-in-JS)
Example with Tailwind:
const Button = () => (
<button className="bg-blue-500 text-white px-4 py-2 rounded">Click me</button>
);
✅ Choose a solution consistent with your stack.
🔐 21. Authentication (with context + route guard)
Use an AuthContext to manage login state and restrict access to routes.
const PrivateRoute = ({ children }) => {
const { isAuthenticated } = useContext(AuthContext);
return isAuthenticated ? children : <Navigate to="/login" />;
};
✅ Combine with JWT or OAuth depending on your backend.
📡 22. React Query: Server-State Management
React Query helps manage server-side state like API calls and caching.
import { useQuery } from '@tanstack/react-query';
const fetchPosts = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
return res.json();
};
const Posts = () => {
const { data, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
✅ Caches, retries, and auto-refetches data efficiently.
🧠 23. Controlled vs Uncontrolled Components
Controlled components rely on React state, while uncontrolled components use refs.
✅ Controlled
const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />;
⚠️ Uncontrolled
const inputRef = useRef();
<input ref={inputRef} />;
✅ Use controlled components for better control and validation.
🌐 24. Fetch API & Data Handling
useEffect
allows fetching data from an API.
const App = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((res) => res.json())
.then((data) => setData(data));
}, []);
return <p>{data?.title}</p>;
};
✅ Use React Query for advanced data management.
📤 25. Forms and Validation
React forms collect user input; validation ensures data integrity.
const Form = () => {
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (name.trim() === '') return alert('Name is required');
console.log(name);
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
};
✅ Use libraries like react-hook-form or Formik for complex forms.
🔍 26. useId: Unique IDs for Accessibility
React 18 introduced useId for generating unique, deterministic IDs.
const Form = () => {
const id = useId();
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" />
</>
);
};
✅ Ensures consistent IDs across server and client renders.
🌐 27. Internationalization (i18n)
Support multiple languages using libraries like react-i18next.
import { useTranslation } from 'react-i18next';
const Greeting = () => {
const { t } = useTranslation();
return <p>{t('welcome_message')}</p>;
};
✅ Helps build globally accessible apps.
🧪 28. Testing React Components
Use libraries like @testing-library/react and Jest.
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders greeting', () => {
render(<App />);
expect(screen.getByText(/welcome/i)).toBeInTheDocument();
});
✅ Ensure reliability and prevent regressions.
🎛 29. Component Composition
Compose UI by nesting smaller components inside larger ones.
const Card = ({ children }) => <div className="card">{children}</div>;
const App = () => (
<Card>
<h2>Hello</h2>
<p>This is inside a card.</p>
</Card>
);
✅ Promotes reusability and clean structure.
🧩 30. Higher-Order Components (HOC)
A function that takes a component and returns a new one with additional props or logic.
const withLogger = (Component) => {
return (props) => {
console.log('Rendering with props:', props);
return <Component {...props} />;
};
};
const Hello = ({ name }) => <p>Hello {name}</p>;
const HelloWithLogger = withLogger(Hello);
✅ Used in legacy codebases, often replaced by hooks.
📦 31. Code Splitting
Break large bundles into smaller chunks with React.lazy and Suspense.
const Settings = React.lazy(() => import('./Settings'));
const App = () => (
<Suspense fallback={<p>Loading Settings...</p>}>
<Settings />
</Suspense>
);
✅ Improves load performance.
🚀 32. Server Components (React 18+)
Server Components are a React 18+ feature (especially used with frameworks like Next.js) that allow components to be rendered entirely on the server, without shipping their JavaScript to the client.
They provide several benefits:
-
Run server-only logic (e.g., database queries, API fetching, authentication)
-
Reduce the amount of JavaScript sent to the browser
-
Improve initial load times and performance
-
Enhance SEO through HTML rendering
📦 Example of a pure Server Component (Next.js app/ directory):
// ServerComponent.tsx
export default async function Post() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await res.json();
return <h1>{data.title}</h1>;
}
✅ This component:
-
Runs only on the server
-
Does not include any JavaScript in the final client bundle
-
Can safely access sensitive data or server APIs
🧠 Differences between Server and Client Components
Server Components:
-
Run only on the server
-
Cannot use hooks like useState, useEffect, etc.
-
Cannot include interactivity
-
Can fetch data securely
-
Do not contribute to the client bundle size
Client Components:
-
Run in the browser
-
Support interactivity and hooks
-
Must be explicitly marked with 'use client';
-
Included in the JS bundle sent to the client
⚙️ Mixing Server and Client Components
You can compose Server and Client Components together.
Example:
// ServerComponent.tsx
import ClientButton from './ClientButton'; // This is a Client Component
export default async function ServerComponent() {
const data = await fetchData();
return (
<div>
<h1>{data.title}</h1>
<ClientButton />
</div>
);
}
In ClientButton.tsx:
'use client';
import { useState } from 'react';
export default function ClientButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
);
}
✅ Use Server Components for non-interactive, data-driven UI.
✅ Use Client Components only when interactivity is needed (e.g., forms, buttons, modals).
🌐 Official Sources
🧰 33. TypeScript with React
TypeScript enables strong static typing for React components.
type Props = { name: string };
const Greeting = ({ name }: Props) => <p>Hello, {name}</p>;
✅ Highly recommended for medium to large-scale projects.
🧪 34. React DevTools
A browser extension to inspect the React component tree, state, and hooks.
✅ Lets you debug props, state, performance bottlenecks, and re-renders visually.
🌳 35. Reconciliation and Virtual DOM
React uses a virtual DOM to optimize rendering.
✅ When state/props change, React:
-
Calculates a new virtual DOM.
-
Diffs it with the previous one.
-
Updates only the changed parts in the real DOM.
⚡ This makes React fast and efficient.
📚 Further Reading & Official Resources
Here are some reliable sources to learn more about React and the concepts covered:
- React Official Documentation — The best place to start and stay up-to-date.
- React Hooks API Reference — Complete reference for built-in hooks.
- React Router Documentation — For client-side routing in React apps.
- React Query (TanStack Query) — Manage server-state and caching efficiently.
- React Hook Form — Lightweight solution for form handling and validation.
- TypeScript with React — Community-maintained cheatsheet for TS + React.
- React DevTools — Inspect components and hooks visually.
- Next.js Documentation — For server-side rendering, file-based routing, and server components.
- Tailwind CSS Documentation — Utility-first CSS framework for styling React apps.
- MDN Web Docs: JavaScript — Core JavaScript concepts underlying React.