React: Fundamentals

January 11, 2025

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

React - Server Components

Next.js - Server Components


🧰 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.

🔗 React Developer Tools


🌳 35. Reconciliation and Virtual DOM

React uses a virtual DOM to optimize rendering.

✅ When state/props change, React:

  1. Calculates a new virtual DOM.

  2. Diffs it with the previous one.

  3. 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: