This shows you the differences between two versions of the page.
|
se:labs:03 [2024/11/01 19:07] cosmin.dumitrache [Lab 03 - Introduction to React] |
se:labs:03 [2025/10/19 20:34] (current) ilie_cristian.sandu [Tasks] |
||
|---|---|---|---|
| Line 17: | Line 17: | ||
| {{:se:labs:react_virtual_dom.jpeg?700|}} | {{:se:labs:react_virtual_dom.jpeg?700|}} | ||
| - | The red circles represent the nodes that have changed. These nodes represent the UI elements that have had their state changed. The difference between the previous version of the virtual DOM tree and the current virtual DOM tree is then calculated. The whole parent subtree then gets re-rendered to give the updated UI. This updated tree is then batch updated to the real DOM. | + | The red circles represent the nodes that have changed. These nodes represent the UI elements that have had their state changed. The difference between the previous version of the virtual DOM tree and the current virtual DOM tree is then calculated. When state or props change, React calls the component function again (i.e. re-renders that component subtree) to produce a new virtual DOM. The reconciliation algorithm then diffs that new tree against the previous one, and only the minimal set of actual DOM operations is applied (batched) to reflect those differences in the real DOM. |
| ===== JSX ===== | ===== JSX ===== | ||
| - | JSX is an XML-like syntax extension to ECMAScript without any defined semantics. | + | JSX is a syntax extension for JavaScript (resembling XML/HTML) that gets compiled (e.g. by Babel/TypeScript) into React.createElement(...) calls (or equivalent). It doesn’t itself have meaning to the browser — it's a compile-time convenience. |
| React embraces the fact that rendering logic is inherently coupled with other UI logic: how events are handled, how the state changes over time, and how the data is prepared for display. | React embraces the fact that rendering logic is inherently coupled with other UI logic: how events are handled, how the state changes over time, and how the data is prepared for display. | ||
| Line 49: | Line 49: | ||
| This modularity allows your code to be more maintainable as it grows because you can easily add, update, and delete components without touching the rest of our application. The nice thing about React components is that they are just JavaScript. | This modularity allows your code to be more maintainable as it grows because you can easily add, update, and delete components without touching the rest of our application. The nice thing about React components is that they are just JavaScript. | ||
| - | In React, components are functions or classes, we are only going to use functions. Inside your script tag, write a function called header: | + | React historically supported class components, but modern React encourages use of functional components and hooks. In this lab, we’ll only use functional components. |
| <code javascript> | <code javascript> | ||
| Line 191: | Line 190: | ||
| } | } | ||
| </code> | </code> | ||
| + | |||
| + | ==== (Optional) Other Useful React Hooks ==== | ||
| + | |||
| + | While `useState()` is the most common React hook, React also provides several others that help you manage side effects, access DOM elements, and optimize performance. | ||
| + | |||
| + | === useEffect === | ||
| + | |||
| + | The `useEffect()` hook lets you perform **side effects** in your components, such as fetching data, updating the document title, or setting up timers. | ||
| + | By default, it runs after every render, but you can control when it runs by specifying dependencies. | ||
| + | |||
| + | <code javascript> | ||
| + | import React, { useState, useEffect } from "react"; | ||
| + | |||
| + | export default function Timer() { | ||
| + | const [seconds, setSeconds] = useState<number>(0); | ||
| + | |||
| + | useEffect(() => { | ||
| + | const interval = setInterval(() => { | ||
| + | setSeconds((s) => s + 1); | ||
| + | }, 1000); | ||
| + | |||
| + | // Cleanup when component unmounts | ||
| + | return () => clearInterval(interval); | ||
| + | }, []); | ||
| + | |||
| + | return <h1>Seconds passed: {seconds}</h1>; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | **Tip:** | ||
| + | * <code javascript> useEffect(() => {...}, []) // → runs only once (on mount) </code> | ||
| + | * <code javascript> useEffect(() => {...}, [value]) // → runs whenever `value` changes </code> | ||
| + | |||
| + | |||
| + | === useRef === | ||
| + | |||
| + | The `useRef()` hook gives you a **mutable reference** that persists across renders without causing re-renders. | ||
| + | It’s useful for accessing DOM elements or storing values like timers. | ||
| + | |||
| + | <code javascript> | ||
| + | import React, { useRef, useEffect } from "react"; | ||
| + | |||
| + | export default function FocusInput() { | ||
| + | const inputRef = useRef<HTMLInputElement>(null); | ||
| + | |||
| + | useEffect(() => { | ||
| + | inputRef.current?.focus(); | ||
| + | }, []); | ||
| + | |||
| + | return <input ref={inputRef} placeholder="Type here..." />; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | === useContext === | ||
| + | |||
| + | The `useContext()` hook allows you to share values (like theme, language, or user info) across the component tree **without prop drilling**. | ||
| + | |||
| + | <code javascript> | ||
| + | import React, { createContext, useContext } from "react"; | ||
| + | |||
| + | const ThemeContext = createContext("light"); | ||
| + | |||
| + | function ThemeDisplay() { | ||
| + | const theme = useContext(ThemeContext); | ||
| + | return <h1>Current theme: {theme}</h1>; | ||
| + | } | ||
| + | |||
| + | export default function App() { | ||
| + | return ( | ||
| + | <ThemeContext.Provider value="dark"> | ||
| + | <ThemeDisplay /> | ||
| + | </ThemeContext.Provider> | ||
| + | ); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | === useMemo and useCallback === | ||
| + | |||
| + | These two hooks are used for **performance optimization**. | ||
| + | |||
| + | * `useMemo()` caches computed values to avoid re-calculating them. | ||
| + | * `useCallback()` caches function references to prevent unnecessary re-renders. | ||
| + | |||
| + | <code javascript> | ||
| + | import React, { useState, useMemo, useCallback } from "react"; | ||
| + | |||
| + | export default function ExpensiveCalculation() { | ||
| + | const [count, setCount] = useState<number>(0); | ||
| + | const [input, setInput] = useState<number>(0); | ||
| + | |||
| + | const squared = useMemo(() => { | ||
| + | console.log("Calculating..."); | ||
| + | return input * input; | ||
| + | }, [input]); | ||
| + | |||
| + | const increment = useCallback(() => setCount((c) => c + 1), []); | ||
| + | |||
| + | return ( | ||
| + | <div> | ||
| + | <input | ||
| + | type="number" | ||
| + | value={input} | ||
| + | onChange={(e) => setInput(Number(e.target.value))} | ||
| + | /> | ||
| + | <p>Square: {squared}</p> | ||
| + | <button onClick={increment}>Count ({count})</button> | ||
| + | </div> | ||
| + | ); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | === useReducer === | ||
| + | |||
| + | The `useReducer()` hook is an alternative to `useState()` for handling **more complex state logic**. | ||
| + | It works similarly to Redux reducers. | ||
| + | |||
| + | <code javascript> | ||
| + | import React, { useReducer } from "react"; | ||
| + | |||
| + | function reducer(state: number, action: string) { | ||
| + | switch (action) { | ||
| + | case "increment": | ||
| + | return state + 1; | ||
| + | case "decrement": | ||
| + | return state - 1; | ||
| + | default: | ||
| + | return state; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | export default function Counter() { | ||
| + | const [count, dispatch] = useReducer(reducer, 0); | ||
| + | |||
| + | return ( | ||
| + | <div> | ||
| + | <p>Count: {count}</p> | ||
| + | <button onClick={() => dispatch("increment")}>+</button> | ||
| + | <button onClick={() => dispatch("decrement")}>-</button> | ||
| + | </div> | ||
| + | ); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | |||
| + | === Summary of Common Hooks === | ||
| + | |||
| + | ^ Hook ^ Purpose ^ | ||
| + | | `useState` | Manage local component state | | ||
| + | | `useEffect` | Handle side effects (fetching data, timers, etc.) | | ||
| + | | `useRef` | Persist values or access DOM elements | | ||
| + | | `useContext` | Share data across components | | ||
| + | | `useMemo` | Cache expensive calculations | | ||
| + | | `useCallback` | Cache function definitions | | ||
| + | | `useReducer` | Manage complex state logic | | ||
| ===== Data Binding ===== | ===== Data Binding ===== | ||
| Line 221: | Line 377: | ||
| <code javascript> | <code javascript> | ||
| const Parent = () => { | const Parent = () => { | ||
| - | const [message, setMessage] = React.useState<string>("Hello World"); | + | const [message, setMessage] = React.useState("Hello World"); |
| - | const chooseMessage = (message: string) => { | + | |
| - | setMessage(message); | + | |
| - | }; | + | |
| return ( | return ( | ||
| <div> | <div> | ||
| <h1>{message}</h1> | <h1>{message}</h1> | ||
| - | <Child chooseMessage={chooseMessage} /> | + | <Child onMessageSelect={setMessage} /> |
| </div> | </div> | ||
| ); | ); | ||
| }; | }; | ||
| - | const Child = ({ chooseMessage }: { chooseMessage: string }) => { | + | |
| - | let msg: string = "Goodbye"; | + | const Child = ({ chooseMessage }: { chooseMessage: (message: string) => void }) => { |
| + | const msg = "Goodbye"; | ||
| return ( | return ( | ||
| - | <div> | + | <button onClick={() => onMessageSelect(msg)}>Change Message</button> |
| - | <button onClick={() => chooseMessage(msg)}>Change Message</button> | + | |
| - | </div> | + | |
| ); | ); | ||
| }; | }; | ||
| Line 247: | Line 400: | ||
| ===== Tailwind CSS ===== | ===== Tailwind CSS ===== | ||
| - | Tailwind CSS is a utility-first CSS framework that allows you to build custom designs quickly and efficiently. Unlike traditional CSS frameworks, which come with predefined components, Tailwind provides low-level utility classes for controlling layout, color, spacing, typography, and more, directly in your HTML. This approach gives you more flexibility without writing custom CSS. | + | Tailwind CSS is a **utility-first CSS framework** that allows you to build custom designs quickly and efficiently. |
| + | Unlike traditional CSS frameworks, which come with predefined components, Tailwind provides low-level utility classes for controlling layout, color, spacing, typography, and more — directly in your React components. | ||
| + | This approach gives you more flexibility without writing custom CSS. | ||
| Some key concepts of Tailwind CSS: | Some key concepts of Tailwind CSS: | ||
| - | * **Utility-First**: Tailwind provides small, single-purpose classes for styling (e.g., `**p-4**` for padding, `**text-center**` for text alignment). | + | * **Utility-First**: Tailwind provides small, single-purpose classes for styling (for example, `p-4` for padding or `text-center` for text alignment). |
| * **Responsive Design**: Tailwind has built-in support for responsive design, enabling you to style elements differently based on screen size. | * **Responsive Design**: Tailwind has built-in support for responsive design, enabling you to style elements differently based on screen size. | ||
| - | * **Customization**: You can customize or extend Tailwind's default configuration, adding your own color palettes, spacing, and more. | + | * **Customization**: You can customize or extend Tailwind's default configuration by adding your own color palettes, spacing, or typography. |
| - | Examples: | + | |
| **Centering Text** | **Centering Text** | ||
| - | To center-align text and add some padding, you can use the `**text-center**` and `**p-4**` utilities: | + | To center-align text and add some padding in React, you can use the `text-center` and `p-4` utilities with `className`: |
| - | <code html> | + | <code javascript> |
| - | <div class="text-center p-4"> | + | export default function CenteredText() { |
| - | <h1 class="text-xl font-bold">Hello, Tailwind CSS!</h1> | + | return ( |
| - | </div> | + | <div className="text-center p-4"> |
| + | <h1 className="text-xl font-bold">Hello, Tailwind CSS!</h1> | ||
| + | </div> | ||
| + | ); | ||
| + | } | ||
| </code> | </code> | ||
| **Responsive Layout** | **Responsive Layout** | ||
| - | With Tailwind, you can easily create responsive layouts using its responsive utilities. For instance, to set different column layouts on different screen sizes, you might do: | + | With Tailwind, you can easily create responsive layouts using its responsive utilities. |
| + | For instance, to set different column layouts on different screen sizes, you might do: | ||
| - | <code html> | + | <code javascript> |
| - | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | + | export default function ResponsiveGrid() { |
| - | <div class="bg-blue-200 p-4">Item 1</div> | + | return ( |
| - | <div class="bg-blue-200 p-4">Item 2</div> | + | <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> |
| - | <div class="bg-blue-200 p-4">Item 3</div> | + | <div className="bg-blue-200 p-4">Item 1</div> |
| - | <div class="bg-blue-200 p-4">Item 4</div> | + | <div className="bg-blue-200 p-4">Item 2</div> |
| - | </div> | + | <div className="bg-blue-200 p-4">Item 3</div> |
| + | <div className="bg-blue-200 p-4">Item 4</div> | ||
| + | </div> | ||
| + | ); | ||
| + | } | ||
| </code> | </code> | ||
| **Button Styling** | **Button Styling** | ||
| - | Tailwind allows you to create buttons with custom styles by combining utility classes: | + | Tailwind allows you to create buttons with custom styles by combining multiple utility classes. |
| + | In React, use `className` for the styling attribute: | ||
| - | <code html> | + | <code javascript> |
| - | <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> | + | export default function StyledButton() { |
| - | Click Me | + | return ( |
| - | </button> | + | <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> |
| + | Click Me | ||
| + | </button> | ||
| + | ); | ||
| + | } | ||
| </code> | </code> | ||
| + | |||
| + | **More Examples** | ||
| + | |||
| + | Tailwind also provides utilities for: | ||
| + | * **Text colors** – `text-red-500`, `text-gray-700`, `text-green-600` | ||
| + | * **Backgrounds** – `bg-blue-100`, `bg-yellow-200` | ||
| + | * **Spacing** – `m-4`, `p-2`, `px-6` | ||
| + | * **Flexbox and grid layouts** – `flex`, `items-center`, `justify-between`, `grid-cols-3` | ||
| + | * **Typography** – `font-bold`, `text-lg`, `tracking-wide` | ||
| + | |||
| + | You can find the full list of utilities and examples in the official Tailwind CSS documentation: | ||
| + | **[[https://tailwindcss.com/docs|https://tailwindcss.com/docs]]** | ||
| + | |||
| + | ===== (Optional) Useful Resources for Modern React Development ===== | ||
| + | |||
| + | Once you understand the basics of React, there are several modern libraries and frameworks that can help you build professional, scalable applications more efficiently. | ||
| + | Below are some of the most widely used tools in the React ecosystem. | ||
| + | |||
| + | === ShadCN/UI === | ||
| + | |||
| + | [[https://ui.shadcn.com/|ShadCN/UI]] is a modern React component library built with **Tailwind CSS** and **Radix UI**. | ||
| + | It provides ready-to-use, accessible, and themable components (buttons, dialogs, data tables, forms, tooltips, etc.) that you can copy directly into your project and customize. | ||
| + | |||
| + | * Designed for flexibility — you own the code of each component. | ||
| + | * Uses Tailwind for styling. | ||
| + | * Fully compatible with Next.js and Vite setups. | ||
| + | |||
| + | **Example (Button component):** | ||
| + | |||
| + | <code javascript> | ||
| + | import { Button } from "@/components/ui/button"; | ||
| + | |||
| + | export default function Example() { | ||
| + | return <Button variant="outline">Click me</Button>; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === TanStack React Query === | ||
| + | |||
| + | [[https://tanstack.com/query/latest|TanStack Query (React Query)]] is a powerful library for **data fetching, caching, and synchronization** in React apps. | ||
| + | |||
| + | It simplifies working with APIs by managing loading states, caching results, refetching automatically, and handling background updates — all with minimal code. | ||
| + | |||
| + | **Example (fetching data):** | ||
| + | |||
| + | <code javascript> | ||
| + | import { useQuery } from "@tanstack/react-query"; | ||
| + | |||
| + | async function fetchUsers() { | ||
| + | const res = await fetch("https://jsonplaceholder.typicode.com/users"); | ||
| + | return res.json(); | ||
| + | } | ||
| + | |||
| + | export default function Users() { | ||
| + | const { data, isLoading, error } = useQuery({ | ||
| + | queryKey: ["users"], | ||
| + | queryFn: fetchUsers, | ||
| + | }); | ||
| + | |||
| + | if (isLoading) return <p>Loading...</p>; | ||
| + | if (error) return <p>Error loading data</p>; | ||
| + | |||
| + | return ( | ||
| + | <ul> | ||
| + | {data.map((user) => ( | ||
| + | <li key={user.id}>{user.name}</li> | ||
| + | ))} | ||
| + | </ul> | ||
| + | ); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === TanStack Table === | ||
| + | |||
| + | [[https://tanstack.com/table/latest|TanStack Table]] (formerly React Table) is a lightweight headless table library for building **interactive and customizable data tables**. | ||
| + | You control how the table is rendered — it only provides the logic for sorting, filtering, pagination, and selection. | ||
| + | |||
| + | **Example (basic table setup):** | ||
| + | |||
| + | <code javascript> | ||
| + | import { useReactTable, getCoreRowModel } from "@tanstack/react-table"; | ||
| + | |||
| + | const data = [ | ||
| + | { name: "Alice", age: 25 }, | ||
| + | { name: "Bob", age: 30 }, | ||
| + | ]; | ||
| + | |||
| + | const columns = [ | ||
| + | { header: "Name", accessorKey: "name" }, | ||
| + | { header: "Age", accessorKey: "age" }, | ||
| + | ]; | ||
| + | |||
| + | export default function BasicTable() { | ||
| + | const table = useReactTable({ | ||
| + | data, | ||
| + | columns, | ||
| + | getCoreRowModel: getCoreRowModel(), | ||
| + | }); | ||
| + | |||
| + | return ( | ||
| + | <table> | ||
| + | <thead> | ||
| + | {table.getHeaderGroups().map((headerGroup) => ( | ||
| + | <tr key={headerGroup.id}> | ||
| + | {headerGroup.headers.map((header) => ( | ||
| + | <th key={header.id}>{header.column.columnDef.header}</th> | ||
| + | ))} | ||
| + | </tr> | ||
| + | ))} | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | {table.getRowModel().rows.map((row) => ( | ||
| + | <tr key={row.id}> | ||
| + | {row.getVisibleCells().map((cell) => ( | ||
| + | <td key={cell.id}>{cell.getValue()}</td> | ||
| + | ))} | ||
| + | </tr> | ||
| + | ))} | ||
| + | </tbody> | ||
| + | </table> | ||
| + | ); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | === Other Notable Tools === | ||
| + | |||
| + | * **[[https://nextjs.org/|Next.js]]** – React framework for building full-stack apps with server-side rendering and routing. | ||
| + | * **[[https://react.dev/learn|React Docs]]** – The official React documentation (excellent for up-to-date examples). | ||
| + | * **[[https://tailwindcss.com/docs|Tailwind CSS Docs]]** – Learn how to customize design tokens, responsive utilities, and themes. | ||
| + | * **[[https://radix-ui.com/|Radix UI]]** – Low-level, accessible UI primitives used by ShadCN/UI. | ||
| + | * **[[https://reactrouter.com/en/main|React Router]]** – Client-side routing for React apps. | ||
| + | * **[[https://formik.org/|Formik]]** and **[[https://react-hook-form.com/|React Hook Form]]** – For building and validating forms easily. | ||
| + | |||
| + | ✅ **Tip:** | ||
| + | These libraries are not required for this lab, but they are very common in real-world React projects and can greatly improve developer productivity and user experience. | ||
| + | |||
| Line 292: | Line 597: | ||
| ====== Tasks ====== | ====== Tasks ====== | ||
| - | - Download the generated project {{:se:labs:se-lab3-tasks.zip|}} (and run `npm install` and `npm start`) | + | - Download the generated project {{:se:labs:se-lab3-tasks.zip|}} (and run **npm install** and **npm run dev**) |
| - Create a to do list app: | - Create a to do list app: | ||
| - | - Implement the List component in `components/ToDoList.tsx` | + | - Implement the List component in **components/ToDoList.tsx** |
| - | - Create a new component in `components/` that will represent __a todo-list item__ that should have: | + | - Create a new component in **components/** that will represent __a todo-list item__ that should have: |
| - text showing the description of the todo-list item | - text showing the description of the todo-list item | ||
| - edit button that enable you to edit todo-item text | - edit button that enable you to edit todo-item text | ||