diff --git a/Frontend/react/code-splitting.md b/Frontend/react/code-splitting.md new file mode 100644 index 00000000..bf8fc829 --- /dev/null +++ b/Frontend/react/code-splitting.md @@ -0,0 +1,263 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'Optimize JavaScript performance with code splitting techniques like route-based splitting, lazy loading, and dynamic imports' +tags: + - 'react' + - 'performance' +title: 'Code Splitting in React' +short_title: 'Code Splitting' +--- + +Code splitting is a technique used to optimize JavaScript bundles by breaking them into smaller chunks, loading only the necessary parts when they’re needed. This reduces the initial loading time for users, as they only download the essential code to render the initial view. Code splitting is particularly valuable in large applications where bundling everything together can lead to slow load times and performance issues. + +We will explore various code splitting techniques, including their use cases and practical implementation examples. + +## Code Splitting Techniques + +### Entry Point Splitting + +Entry point splitting involves separating the main application entry points. In Webpack, you can specify multiple entry points, each generating a separate bundle. This technique is helpful in multi-page applications (MPAs) or if you have clearly separate sections within a single-page app (SPA) that can load independently. + +```js +// webpack.config.js +module.exports = { + entry: { + home: './src/home.js', + dashboard: './src/dashboard.js', + }, + output: { + filename: '[name].bundle.js', + path: __dirname + '/dist', + }, +} +``` + +Here, Webpack creates `home.bundle.js` and `dashboard.bundle.js`, loading only the necessary code when the user navigates to either the home page or dashboard. + +**Use Case:** + +- Ideal for MPAs or complex SPAs where different parts of the app can load independently. + +### Route-Based Code Splitting + +Route-based code splitting is common in SPAs. Instead of loading the entire app at once, only the components needed for the current route are loaded initially. Additional routes are loaded only when the user navigates to them. + +**Example with React Router and React.lazy:** + +```jsx +import React, { lazy, Suspense } from 'react' +import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' + +const Home = lazy(() => import('./Home')) +const About = lazy(() => import('./About')) +const Contact = lazy(() => import('./Contact')) + +function App() { + return ( + + Loading...}> + + + + + + + + ) +} +``` + +**Explanation:** + +- `React.lazy` dynamically imports each component. +- `Suspense` shows a fallback (loading spinner) while the component loads. + +**Benefits:** + +- Reduces initial load time, as only the code for the first route is loaded. +- Each route loads on demand, improving perceived performance for users. + +### Component-Level Code Splitting with React.lazy and Suspense + +If you have a large component that doesn’t need to load right away (e.g., a modal or sidebar), you can split it out and load it only when it’s needed. This helps reduce the initial bundle size, as non-essential components load asynchronously. + +**Example: Lazy Loading a Component** + +```jsx +import React, { lazy, Suspense, useState } from 'react' + +const UserProfile = lazy(() => import('./UserProfile')) + +function App() { + const [showProfile, setShowProfile] = useState(false) + + return ( +
+ + Loading...
}>{showProfile && } + + ) +} +``` + +**Explanation:** + +- The `UserProfile` component only loads when showProfile is true. +- `Suspense` ensures that a fallback UI (loading spinner) is displayed while `UserProfile` is being loaded. + +**Use Cases:** + +- Large, non-essential components such as modals, drawers, or other sections that users may not access right away. + +### Splitting Large Dependencies or Utilities + +Sometimes a single library or utility can significantly increase your bundle size. Instead of loading the entire library, use dynamic `import()` to load only the necessary part of the code when needed. This is particularly useful for utilities like date formatting or image processing libraries that may not be required on every page. + +**Example: Lazy Loading a Utility Library** + +```jsx +function DateFormatter({ date }) { + const [formattedDate, setFormattedDate] = useState('') + + useEffect(() => { + async function loadDateLibrary() { + const { format } = await import('date-fns') + setFormattedDate(format(new Date(date), 'yyyy-MM-dd')) + } + loadDateLibrary() + }, [date]) + + return
{formattedDate}
+} +``` + +**Explanation:** + +- The date-fns library only loads when DateFormatter is rendered. +- This avoids including the entire library in the initial bundle, saving on bundle size. + +**Benefits:** + +- Reduces initial load time by avoiding unnecessary libraries in the main bundle. +- Load dependencies only when needed, improving performance and responsiveness. + +### Library-Based Code Splitting with `react-loadable` + +For more complex loading scenarios, `react-loadable` offers additional features such as delayed loading, error boundaries, and preloading. It’s especially helpful if you want to provide a custom loading experience or handle loading errors gracefully. + +**Example Using react-loadable** + +```jsx +import Loadable from 'react-loadable' + +const LoadableComponent = Loadable({ + loader: () => import('./HeavyComponent'), + loading: ({ isLoading, pastDelay, error }) => { + if (isLoading && pastDelay) return
Loading...
+ if (error) return
Error loading component!
+ return null + }, + delay: 300, // Shows loading only if loading takes longer than 300ms +}) + +function App() { + return +} +``` + +**Explanation:** + +- `react-loadable` provides a loading component that displays based on certain conditions, such as past delay time or error occurrence. +- This allows you to handle cases where loading might take a long time or fail altogether, providing a better user experience. + +**Use Cases:** + +- Components that may take longer to load due to their size or network conditions. +- Error-prone components that need error handling. + +## Advanced Code Splitting Techniques + +### Preloading and Prefetching Components + +Preloading and prefetching are useful when you want to load components in advance, either to improve performance or to anticipate user interactions. + +- **Preload**: Load code for a component in the background, without delaying the initial page load. +- **Prefetch**: Load code when the user is likely to need it soon, based on user interaction patterns (e.g., hovering over a link). + +```jsx +const UserProfile = lazy(() => import(/* webpackPrefetch: true */ './UserProfile')) +``` + +**Use Case:** + +- Preload the next route’s component in the background while the user is interacting with the current route. + +### Bundle Splitting + +Bundling tools, such as Webpack, that have the `SplitChunksPlugin` component can be configured to automatically separate common dependencies (like react or lodash) into distinct bundles. This avoids redundant code in each chunk and reduces the total bundle size. + +**Example Configuration in Webpack:** + +```js +module.exports = { + optimization: { + splitChunks: { + chunks: 'all', + minSize: 30000, + maxSize: 50000, + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + }, + }, + }, + }, +} +``` + +**Explanation**: + +- SplitChunksPlugin creates a vendors chunk with common dependencies, reducing redundancy and improving caching. + +**Use Case:** + +- In large applications with many shared dependencies. + +### Lazy Loading Images and Assets + +For non-JavaScript assets like images and fonts, you can also improve performance by loading them only when they’re in the viewport. + +**Example: Lazy Loading Images with `loading="lazy"`** + +```jsx +function ImageComponent() { + return Lazy loaded image +} +``` + +**Explanation:** + +- The loading="lazy" attribute ensures the image loads only when it’s about to enter the viewport. + +**Benefits:** + +- Reduces initial data transfer, helping pages load faster, especially when there are many images. + +### Summary + +| Technique | Best For | Examples | +| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------ | +| **Entry Point Splitting** | Multi-page apps with separate entry points | Home, Admin, Dashboard entry points | +| **Route-Based Splitting** | Single-page apps, lazy loading route components | Lazy loading routes with React Router | +| **Component-Level Splitting** | Large components like modals, settings panels | Lazy loading non-essential components | +| **Large Dependency Splitting** | Libraries used infrequently | Date formatting utilities, large image processing libs | +| **Library-Based Splitting** | Components that need advanced loading/error handling | `react-loadable` for complex loading states | +| **Preloading and Prefetching** | Anticipating user actions to improve UX | Preloading next route or component | +| **Bundle Splitting** | Avoiding redundancy by splitting common dependencies | Splitting `vendors` bundle | +| **Lazy Loading Images** | Reducing initial page weight for media-rich applications | `loading="lazy"` attribute on images | + +Each of these techniques targets a specific aspect of load management and bundle optimization, providing flexibility to load only what’s necessary. Applying them strategically improves both the initial load time and the user experience throughout the app, especially as users navigate or interact more deeply with various parts of the application. diff --git a/Frontend/react/component-communication-and-decoupling.md b/Frontend/react/component-communication-and-decoupling.md new file mode 100644 index 00000000..83ecf1e3 --- /dev/null +++ b/Frontend/react/component-communication-and-decoupling.md @@ -0,0 +1,300 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'Explore essential techniques like props drilling, Context API, custom hooks, and event emitters' +tags: + - 'react' +title: 'Component Communication and Decoupling in React' +short_title: 'Component Communication and Decoupling' +--- + +Component communication and decoupling are crucial in React, especially for large applications where components may need to share data or trigger actions without being tightly coupled. Decoupling allows components to be reusable, maintainable, and flexible, reducing the risk of cascading changes across the app. + +**Core Patterns for Component Communication and Decoupling** + +1. Props Drilling (Direct Parent-Child Communication) +2. Lifting State Up +3. Context API for Shared State +4. Event Emitter (Pub/Sub) Pattern for Loose Coupling +5. Redux, Zustand, or Global Store for Cross-Component State +6. Custom Hooks for Shared Logic + +### Props Drilling (Direct Parent-Child Communication) + +Props drilling refers to passing data through multiple component layers until it reaches the intended child component. While simple, it can quickly become unmanageable as the component tree grows deeper. + +**Example: Direct Data Passing through Props** + +```js +function Parent() { + const [message, setMessage] = useState('Hello from Parent!') + + return +} + +function Child({ message }) { + return +} + +function GrandChild({ message }) { + return
{message}
+} +``` + +**When to Use Props Drilling**: + +- When the data is only needed by a single child or a small subtree. +- Avoid in deeply nested trees or large apps where data needs to be accessed at many levels. + +**Drawbacks**: + +- Scalability issues as the app grows. +- Makes refactoring difficult since any change in the middle component chain affects multiple components. + +### Lifting State Up + +Lifting state up means moving the shared state to the closest common ancestor of the components that need to share it. This approach encourages clear data flow and keeps the components tightly coupled only to the extent needed. + +**Example: Managing Form State Across Fields** + +```js +function Form() { + const [formState, setFormState] = useState({ name: '', email: '' }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormState((prev) => ({ ...prev, [name]: value })) + } + + return ( +
+ + +
+ ) +} + +function NameField({ value, onChange }) { + return +} + +function EmailField({ value, onChange }) { + return +} +``` + +**When to Use Lifting State Up**: + +- Useful when a group of sibling components needs to share data or rely on a single source of truth. +- Works well for managing form data, settings, or small sections of shared UI state. + +**Drawbacks**: + +- Can make parent components bulky and harder to manage if used excessively in large apps. + +### Context API for Shared State + +The Context API enables you to share data across components without drilling props down through multiple layers. It’s ideal for relatively static or infrequently updated global state, such as theme or user settings. + +```js +const ThemeContext = React.createContext() + +function ThemeProvider({ children }) { + const [theme, setTheme] = useState('light') + + const toggleTheme = () => { + setTheme((prev) => (prev === 'light' ? 'dark' : 'light')) + } + + return {children} +} + +function ThemedComponent() { + const { theme, toggleTheme } = useContext(ThemeContext) + return ( +
+ +
+ ) +} +``` + +**When to Use Context API**: + +- When you need to share relatively static data or infrequent updates across multiple components (e.g., theme, language, auth). +- Avoid for frequently updated data as it can lead to unnecessary re-renders across the component tree. + +**Drawbacks**: + +- Overusing context for dynamic or frequently changing state can lead to performance bottlenecks. +- Context is less suitable for data that changes rapidly or is complex (consider Redux/Zustand for such cases). + +### Event Emitter (Pub/Sub) Pattern for Loose Coupling + +The event emitter pattern allows components to communicate by publishing and subscribing to events. This decouples the components, as they don’t need to know each other’s presence—only the event itself. + +**Example: Basic Event Emitter** + +You could create a simple event emitter utility to allow different parts of the app to subscribe to and emit events. + +```js +// EventEmitter.js +export const EventEmitter = { + events: {}, + subscribe(event, callback) { + if (!this.events[event]) this.events[event] = [] + this.events[event].push(callback) + }, + emit(event, data) { + if (this.events[event]) this.events[event].forEach((callback) => callback(data)) + }, +} + +// Usage in components: +function ComponentA() { + useEffect(() => { + EventEmitter.emit('message', 'Hello from ComponentA') + }, []) + + return
Component A
+} + +function ComponentB() { + useEffect(() => { + EventEmitter.subscribe('message', (msg) => alert(msg)) + }, []) + + return
Component B
+} +``` + +**When to Use Event Emitters**: + +- Useful when you need decoupled communication between non-related components. +- Ideal for notifications, global events, or handling loosely coupled actions. + +**Drawbacks**: + +- Overuse can make the data flow unpredictable and hard to trace. +- Debugging is more complex, as event emissions are asynchronous and may be harder to track down. + +### Global Store (Redux/Zustand) for Cross-Component State + +Global stores like Redux or Zustand provide a single source of truth for application-wide state. This is essential when different parts of the app need to access or manipulate shared data, especially if it’s complex or requires consistent behavior. + +**Example: Notifications with Redux** + +Using Redux for managing notifications, components can dispatch actions to add or remove notifications without needing direct knowledge of where or how they’re displayed. + +```js +// notificationSlice.js +import { createSlice } from '@reduxjs/toolkit' + +const notificationSlice = createSlice({ + name: 'notifications', + initialState: [], + reducers: { + addNotification: (state, action) => { + state.push(action.payload) + }, + removeNotification: (state, action) => { + return state.filter((notif) => notif.id !== action.payload) + }, + }, +}) + +export const { addNotification, removeNotification } = notificationSlice.actions +export default notificationSlice.reducer + +// Usage in components: +function AddNotificationButton() { + const dispatch = useDispatch() + const handleAddNotification = () => { + dispatch(addNotification({ id: Date.now(), message: 'New Notification' })) + } + + return +} + +function NotificationList() { + const notifications = useSelector((state) => state.notifications) + + return ( +
    + {notifications.map((notif) => ( +
  • {notif.message}
  • + ))} +
+ ) +} +``` + +**When to Use Global Store**: + +- When multiple parts of the app need access to complex, consistent data. +- For cross-cutting data like notifications, authentication, or async data that requires global consistency. + +**Drawbacks**: + +- Can be overkill for small or medium applications. +- Adds complexity and boilerplate, though libraries like Redux Toolkit reduce some of this overhead. + +### Custom Hooks for Shared Logic + +Custom hooks are a highly effective way to encapsulate and share logic, especially side effects, across components. Hooks allow shared functionality to be implemented in multiple components without duplication or tight coupling. + +**Example: Shared Fetching Logic with Custom Hook** + +```jsx +function useFetch(url) { + const [data, setData] = useState(null) + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + setLoading(true) + fetch(url) + .then((response) => response.json()) + .then((data) => setData(data)) + .catch((error) => setError(error)) + .finally(() => setLoading(false)) + }, [url]) + + return { data, error, loading } +} + +// Usage in components: +function UserList() { + const { data: users, loading, error } = useFetch('/api/users') + + if (loading) return
Loading...
+ if (error) return
Error loading users
+ + return ( +
    + {users.map((user) => ( +
  • {user.name}
  • + ))} +
+ ) +} +``` + +**When to Use Custom Hooks**: + +- For sharing logic that involves side effects or reusable state management. +- To avoid HOCs or render props in cases where hooks are simpler and more readable. + +**Drawbacks**: + +- Custom hooks should be modular and focused on a single concern to avoid complexity. +- Managing dependencies in hooks can be challenging and requires careful planning. + +### Key Takeaways + +- **Direct Communication** (props drilling, lifting state) works for small, simple hierarchies. +- **Context API** suits lightweight shared state, avoiding heavy re-renders. +- **Event Emitters** allow decoupled, loosely coupled communication but can lead to debugging complexity. +- **Global Store (Redux, Zustand**) is essential for complex, cross-cutting state needs. +- **Custom Hooks** are ideal for encapsulating reusable side effects and shared logic without extra component layers. diff --git a/Frontend/react/component-composition-patterns.md b/Frontend/react/component-composition-patterns.md new file mode 100644 index 00000000..59b89d6e --- /dev/null +++ b/Frontend/react/component-composition-patterns.md @@ -0,0 +1,187 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'Learn React composition patterns with coverage of HOCs, render props, compound components, and custom hook' +tags: + - 'react' +title: 'Component Composition Patterns in React' +short_title: 'Component Composition Patterns' +--- + +Component composition patterns are foundational for creating scalable, flexible, and reusable React components. They allow us to build UIs by combining smaller, single-purpose components in various ways. + +## Key Composition Patterns in React + +### Higher-Order Components (HOCs) + +HOCs are functions that take a component and return a new component, adding additional functionality. They’re particularly useful for cross-cutting concerns like logging, analytics, or authentication. + +**Example Use Case**: Suppose you need to add logging functionality to multiple components. Instead of embedding logging code in each component, you create an HOC that wraps each component and handles the logging logic. + +```js +function withLogging(WrappedComponent) { + return function EnhancedComponent(props) { + useEffect(() => { + console.log(`Component ${WrappedComponent.name} mounted`) + }, []) + return + } +} +``` + +**When to Use HOCs**: + +- For injecting props or shared behavior across multiple components. +- To handle cross-cutting concerns that aren’t tightly coupled with the component’s core logic. +- When you want to avoid prop drilling by creating an abstraction layer. + +**Trade-offs**: + +- Can lead to “wrapper hell” if overused. +- Less popular with modern hooks as custom hooks can sometimes achieve similar results in a more straightforward way. + +### Render Props + +With render props, a component uses a prop as a function to control its output, allowing you to pass dynamic rendering logic. + +**Example Use Case**: If you have a `` component that retrieves data, you could use render props to define how that data should be rendered by the consuming component. + +```js +function DataFetcher({ render }) { + const [data, setData] = useState(null) + useEffect(() => { + fetchData().then(setData) + }, []) + return render(data) +} + +// Usage: +; } /> +``` + +**When to Use Render Props**: + +- For providing control over how the child component renders data. +- When the consuming component needs flexibility in rendering but also needs the data or logic encapsulated in the parent. + +**Trade-offs**: + +- Can lead to deeply nested code if not structured thoughtfully. +- Not as widely used with hooks and context, which often simplify sharing functionality across components. + +### Compound Components + +Compound components are components that work together as a single unit but allow for great customization of individual parts. + +**Example Use Case**: A `` component that lets you use `` and `` as children, giving flexibility to control each part while keeping the structure consistent. + +```js +function Dropdown({ children }) { + const [isOpen, setIsOpen] = useState(false) + return ( + +
{children}
+
+ ) +} + +Dropdown.Toggle = function Toggle() { + const { setIsOpen } = useContext(DropdownContext) + return +} + +Dropdown.Menu = function Menu({ children }) { + const { isOpen } = useContext(DropdownContext) + return isOpen ?
{children}
: null +} +``` + +**When to Use Compound Components**: + +- When you have related components that need to work together in a coordinated way but require customization for each part. +- Especially effective for components like modals, tabs, or dropdowns. + +**Trade-offs**: + +- Requires careful management of context to avoid coupling. +- If the API isn’t intuitive, can be confusing for developers unfamiliar with the compound pattern. + +### Controlled and Uncontrolled Components + +Controlled components let the parent manage the state, whereas uncontrolled components manage their own state internally. Combining them allows more flexibility in form components. + +**Example Use Case**: A `` component that can work either as a controlled component (with value and onChange passed from the parent) or an uncontrolled component (handling its own state). + +```js +function TextInput({ value, defaultValue, onChange }) { + const [internalValue, setInternalValue] = useState(defaultValue); + const isControlled = value !== undefined; + + const handleChange = (e) => { + const newValue = e.target.value; + if (isControlled) { + onChange(newValue); + } else { + setInternalValue(newValue); + } + }; + + return ( + + ); +} + +// Usage: + + +``` + +**When to Use Controlled and Uncontrolled Components**: + +- Controlled components are essential for forms or any element that requires data validation. +- Uncontrolled components are more performant and simpler for elements without validation needs. + +**Trade-offs**: + +- Controlled components can lead to performance issues in large forms. +- Uncontrolled components lack flexibility for handling validation or dynamic data flow. + +### Custom Hooks + +Custom hooks provide a way to abstract and encapsulate complex logic, making components more modular and readable. They can be used in place of certain HOCs and render props for handling things like async data, complex state, or side effects. + +**Example Use Case**: If you frequently need to fetch data in multiple components, a `useFetch` custom hook encapsulates this logic, making the components cleaner and more testable. + +```js +function useFetch(url) { + const [data, setData] = useState(null) + useEffect(() => { + fetch(url) + .then((response) => response.json()) + .then(setData) + }, [url]) + return data +} +``` + +**When to Use Custom Hooks**: + +- When encapsulating side effects, data fetching, or complex state logic. +- As a more readable and flexible alternative to HOCs and render props. + +**Trade-offs**: + +- Dependency management in hooks can be tricky, especially with complex dependencies. +- Not ideal for injecting UI-related functionality that might be easier to handle with HOCs or render props. + +## Choosing the Right Pattern + +Selecting the right pattern often depends on: + +- **Complexity of data flow**: If your data flow is complex, consider compound components or render props. +- **Component reusability**: HOCs are ideal for reusable logic across unrelated components. +- **Level of control**: Controlled/uncontrolled patterns are great for balancing simplicity and flexibility in form handling. diff --git a/Frontend/react/design-system-integration.md b/Frontend/react/design-system-integration.md new file mode 100644 index 00000000..08ff0980 --- /dev/null +++ b/Frontend/react/design-system-integration.md @@ -0,0 +1,293 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'Learn how to integrate design systems in React applications with comprehensive coverage of design tokens, atomic components, and accessibility standards.' +tags: + - 'react' + - 'design-system' + - 'storybook' +title: 'Design System Integration in React' +short_title: 'Design System Integration' +--- + +Design system integration in React involves creating a set of reusable, consistent, and easily maintainable components that reflect your app’s design guidelines. Integrating a design system helps ensure visual and functional consistency across your application while allowing for scalability as new components and features are added. Design systems often include **UI components**, **design tokens**, **typography**, **colors**, **icons**, **spacing guidelines**, and **accessibility standards**. + +**Key Steps and Concepts for Design System Integration in React** + +1. Define the Core Design Tokens (colors, typography, spacing, etc.) +2. Build Atomic Components (buttons, inputs, typography) +3. Create Composable, Flexible Components +4. Establish and Use a Component Library Framework (Storybook) +5. Implement Accessibility Standards +6. Use Context and Theming for Adaptable Designs + +### 1. Define Core Design Tokens + +**Design tokens** are the fundamental building blocks of your design system. They represent design decisions like colors, typography, and spacing in a consistent, reusable format. By defining tokens, you create a single source of truth that makes updates and adjustments easy across the app. + +**Example: Design Tokens in a JSON Format** + +```json +{ + "colors": { + "primary": "#007bff", + "secondary": "#6c757d", + "background": "#f8f9fa", + "text": "#212529" + }, + "typography": { + "fontFamily": "Arial, sans-serif", + "fontSize": { + "small": "12px", + "medium": "16px", + "large": "24px" + } + }, + "spacing": { + "small": "4px", + "medium": "8px", + "large": "16px" + } +} +``` + +You can then access and apply these tokens in your components, ensuring they follow consistent visual guidelines. + +**In a React Component:** + +```js +import tokens from './tokens.json' + +const Button = ({ children }) => ( + +) +``` + +### 2. Build Atomic Components + +Atomic design breaks down UI elements into **atoms**, **molecules**, **organisms**, **templates**, and **pages**. This approach helps in building reusable, low-level components that can be combined and customized to create more complex components and layouts. + +**Atomic Design Hierarchy:** + +- **Atoms**: Smallest, single-purpose components like buttons, inputs, or labels. +- **Molecules**: Combinations of atoms, such as an input field with a label. +- **Organisms**: Groups of molecules that form distinct sections, like a header or a form. +- **Templates and Pages**: Higher-level layouts or complete screens that use organisms and molecules. + +**Example: Button Component (Atom)** + +```jsx +const Button = ({ label, onClick, variant = 'primary' }) => { + const styles = { + primary: { + backgroundColor: tokens.colors.primary, + color: tokens.colors.background, + }, + secondary: { + backgroundColor: tokens.colors.secondary, + color: tokens.colors.background, + }, + } + + return ( + + ) +} +``` + +**Example: Form Component (Molecule)** + +```jsx +const FormField = ({ label, type = 'text', value, onChange }) => ( +
+ + +
+) +``` + +### 3. Create Composable, Flexible Components + +Design systems benefit from **flexible components** that can adapt to different use cases without being overly rigid. Use **props** and **styled-system** libraries (e.g., styled-components or emotion) to make your components customizable. + +```jsx +import styled from 'styled-components' +import tokens from './tokens.json' + +const Button = styled.button` + padding: ${tokens.spacing.medium}; + font-size: ${tokens.typography.fontSize.medium}; + color: ${({ variant }) => tokens.colors[variant]}; + background-color: ${({ bg }) => bg || tokens.colors.background}; +` +``` + +This approach allows the Button component to be reusable, enabling different colors and backgrounds with minimal code. + +### 4. Establish and Use a Component Library Framework (Storybook) + +**Storybook** is a popular tool for creating isolated component libraries, documenting components, and allowing team members to interact with components outside of the application environment. + +1. **Set up Storybook**: Install Storybook in your React project. + +```sh +npx sb init +``` + +2. **Write Stories for Components**: Each component should have its own story, describing its appearance with various props and states. + +**Example: Button.stories.js** + +```jsx +import React from 'react' +import { Button } from './Button' + +export default { + title: 'Design System/Button', + component: Button, +} + +export const Primary = () => +) +``` + +- **Keyboard Navigation**: Ensure that all components can be navigated via keyboard. Focus management is crucial, especially for modal dialogs or dynamic components like carousels. + +**Example: Focus Management in a Modal** + +```jsx +import { useEffect, useRef } from 'react' + +const Modal = ({ isOpen, onClose, children }) => { + const closeButtonRef = useRef() + + useEffect(() => { + if (isOpen) { + closeButtonRef.current.focus() + } + }, [isOpen]) + + return isOpen ? ( +
+ + {children} +
+ ) : null +} +``` + +Tools: + +- `axe-core` and `eslint-plugin-jsx-a11y` for testing accessibility issues. +- `Storybook accessibility addon` to validate and fix issues while developing components. + +### 6. Use Context and Theming for Adaptable Designs + +To support **themes** (e.g., dark and light modes), use **context providers** to pass theme values down to components. This enables consistent theming and allows the user to switch themes easily. + +**Example: Theming with Context** + +**1. Create Theme Context:** + +```jsx +import React, { createContext, useContext, useState } from 'react' +import tokens from './tokens.json' + +const ThemeContext = createContext() + +export const ThemeProvider = ({ children }) => { + const [theme, setTheme] = useState('light') + + const toggleTheme = () => setTheme((prev) => (prev === 'light' ? 'dark' : 'light')) + + const themeStyles = theme === 'light' ? tokens.light : tokens.dark + + return {children} +} + +export const useTheme = () => useContext(ThemeContext) +``` + +**2. Consume Theme Context in Components:** + +```jsx +const ThemedButton = ({ label }) => { + const { themeStyles } = useTheme() + return ( + + ) +} +``` + +**3. Switch Themes in App:** + +```jsx +function App() { + const { toggleTheme } = useTheme() + return ( +
+ + +
+ ) +} +``` + +### Summary + +| Technique | Purpose | +| ----------------------------------- | ---------------------------------------------------------------------------------------- | +| **Design Tokens** | Centralize colors, typography, and spacing for consistent styling | +| **Atomic Components** | Build scalable, reusable components from basic building blocks | +| **Composable, Flexible Components** | Ensure flexibility for various use cases using props and dynamic styling | +| **Storybook** | Document, test, and showcase all components, ensuring design and functionality alignment | +| **Accessibility Standards** | Improve usability for all users, following WCAG and ARIA best practices | +| **Context and Theming** | Support adaptable designs, allowing for light and dark themes or other custom themes | + +By integrating these techniques, you can build a design system that is robust, adaptable, and consistent, making it easier to scale and maintain the UI over time. diff --git a/Frontend/react/hook-architecture.md b/Frontend/react/hook-architecture.md new file mode 100644 index 00000000..9ea00368 --- /dev/null +++ b/Frontend/react/hook-architecture.md @@ -0,0 +1,327 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'React hooks architecture with in-depth coverage of custom hooks, state management, and side effects handling.' +tags: + - 'react' + - 'hook' +title: 'Hook Architecture in React' +short_title: 'Hook Architecture' +--- + +Hooks architecture in React refers to the systematic approach of using hooks to manage state, side effects, and reusable logic across components. **Custom hooks** are one of the most powerful features, allowing you to encapsulate and reuse complex logic independently of component structure. Custom hooks improve code readability, keep components lean, and make stateful logic portable and composable. + +### Key Concepts in Hooks Architecture + +1. **Separation of Concerns with Custom Hooks** +2. **Encapsulating Side Effects** +3. **Dependency Management in Hooks** +4. **Combining Multiple Custom Hooks** +5. **Best Practices for Custom Hooks** + +--- + +### 1. Separation of Concerns with Custom Hooks + +By creating custom hooks, we can isolate specific pieces of logic or state, making components simpler and easier to test. Custom hooks follow the same naming conventions and usage patterns as built-in hooks but encapsulate domain-specific or app-specific logic. + +**Example: `useFetch` Hook for Data Fetching** + +```jsx +import { useState, useEffect } from 'react' + +function useFetch(url) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + setLoading(true) + fetch(url) + .then((response) => response.json()) + .then((data) => setData(data)) + .catch((error) => setError(error)) + .finally(() => setLoading(false)) + }, [url]) + + return { data, loading, error } +} +``` + +**Usage**: + +```js +function UserProfile({ userId }) { + const { data, loading, error } = useFetch(`/api/users/${userId}`) + + if (loading) return

Loading...

+ if (error) return

Error loading data.

+ + return
{data.name}
+} +``` + +**Benefits**: + +- **Reusability**: The `useFetch` hook can be used in any component needing data from an API. +- **Isolation of Concerns**: Data fetching logic is isolated, keeping components focused on UI and presentation. + +### 2. Encapsulating Side Effects + +Side effects (like fetching data, managing subscriptions, or setting timeouts) often clutter component code. By moving these side effects into custom hooks, we can encapsulate the logic and improve component readability. + +**Example: `useDocumentTitle` Hook for Updating the Document Title** + +```js +import { useEffect } from 'react' + +function useDocumentTitle(title) { + useEffect(() => { + document.title = title + }, [title]) +} +``` + +**Usage**: + +```js +function HomePage() { + useDocumentTitle('Home - My App') + return
Welcome to the Home Page
+} +``` + +**Benefits**: + +- **Isolation of Side Effects**: The document title logic is separate from the component's main UI, simplifying the component. +- **Reusability**: `useDocumentTitle` can be reused across pages or components that need to set the document title. + +### 3. Dependency Management in Hooks + +Custom hooks require careful handling of dependencies to avoid bugs, stale data, or unintended behaviors. `useEffect`, `useMemo`, and `useCallback` hooks depend on stable dependencies to function predictably. + +**Example: Managing Dependencies with `useMemo`** + +Suppose we need to calculate an expensive value in a hook, which depends on certain props or state. Using `useMemo` ensures the computation only runs when necessary. + +```js +import { useMemo } from 'react' + +function useExpensiveCalculation(data) { + const result = useMemo(() => { + // Expensive calculation here + return data.reduce((sum, num) => sum + num, 0) + }, [data]) + + return result +} +``` + +**Usage**: + +```js +function Stats({ numbers }) { + const total = useExpensiveCalculation(numbers) + return
Total: {total}
+} +``` + +**Benefits**: + +- **Efficiency**: By memoizing the result, we avoid recalculating every render, improving performance. +- **Stable Dependencies**: Carefully setting dependencies ensures the calculation only reruns when `data` changes. + +> In the future, this step will be handled automatically by [React Compiler](https://react.dev/learn/react-compiler#what-does-the-compiler-do) + +### 4. Combining Multiple Custom Hooks + +For more complex scenarios, multiple custom hooks can be combined, keeping components modular and avoiding deeply nested hooks. You can chain hooks to build up increasingly complex functionality without cluttering a single hook. + +**Example: Using `useAuth` and `useFetch` Together** + +```js +// useAuth.js import { useState } from "react"; + +function useAuth() { + const [user, setUser] = useState(null) + + const login = (userData) => setUser(userData) + const logout = () => setUser(null) + + return { user, login, logout } +} + +// useUserData.js import useFetch from "./useFetch"; + +function useUserData(userId) { + const { data, loading, error } = useFetch(`/api/users/${userId}`) + return { data, loading, error } +} + +// Usage in a component + +function Dashboard({ userId }) { + const { user, login, logout } = useAuth() + const { data: userData, loading } = useUserData(userId) + + return ( +
+ {user ? ( +
+ + {loading ?

Loading user data...

:

Welcome, {userData.name}

} +
+ ) : ( + + )} +
+ ) +} +``` + +**Benefits**: + +- **Modularity**: Each hook handles a specific concern (auth or fetching data), so they're independently reusable and testable. +- **Encapsulation**: The component doesn't need to understand the logic inside each hook, only the returned data and functions. + +### 5. Best Practices for Custom Hooks + +1. **Use Clear Naming Conventions**: Name hooks descriptively, starting with `use`, such as `useAuth`, `useFetchData`, or `useToggle`. This helps with readability and code consistency. + +2. **Return Only Necessary Data and Functions**: Custom hooks should return only the data and functions the component actually needs. This minimizes the hook's API surface and reduces complexity. + +```js +// Better: return only what's needed +function useToggle(initialState = false) { + const [state, setState] = useState(initialState) + const toggle = () => setState((prev) => !prev) + return [state, toggle] +} +``` + +1. **Handle Edge Cases and Errors Gracefully**: Build error handling directly into hooks, where applicable. This keeps components from dealing with low-level error handling, focusing only on displaying relevant information to the user. + +```js +function useFetch(url) { + const [data, setData] = useState(null) + const [error, setError] = useState(null) + + useEffect(() => { + fetch(url) + .then((response) => response.json()) + .then(setData) + .catch(setError) + }, [url]) + + return { data, error } +} +``` + +1. **Encapsulate Complex State Logic**: If you find yourself managing complex state logic (e.g., multiple variables, resetting state), consider using `useReducer` within the hook. + +**Example: Managing Form State with `useReducer`** + +```jsx +import { useReducer } from 'react' + +function formReducer(state, action) { + switch (action.type) { + case 'update': + return { ...state, [action.field]: action.value } + case 'reset': + return action.initialState + default: + return state + } +} + +function useForm(initialState) { + const [state, dispatch] = useReducer(formReducer, initialState) + + const updateField = (field, value) => dispatch({ type: 'update', field, value }) + const resetForm = () => dispatch({ type: 'reset', initialState }) + + return [state, updateField, resetForm] +} +``` + +1. **Testing Custom Hooks**: Test custom hooks in isolation to ensure they behave as expected under various scenarios. Tools like **React Testing Library's `renderHook`** make it easy to test hooks directly. + +```js +import { renderHook, act } from '@testing-library/react-hooks' +import useToggle from './useToggle' + +test('should toggle state', () => { + const { result } = renderHook(() => useToggle()) + + act(() => { + result.current[1]() // Call toggle function + }) + + expect(result.current[0]).toBe(true) // Assert the toggled state +}) +``` + +### Combining Techniques in a Custom Hook System + +Imagine you need a custom hook system for managing user authentication, including login, logout, fetching user data, and handling user permissions. We'll create modular hooks that interact but remain individually reusable. + +1. **`useAuth` for Authentication**: Manages login and logout functions and holds user session data. +2. **`useUserData` for Data Fetching**: Fetches user-specific data from the server. +3. **`usePermissions` for Role-Based Access**: Checks permissions based on the user's roles. + +**Combining Custom Hooks**: + +```js +// useAuth.js +function useAuth() { + const [user, setUser] = useState(null) + + const login = (userData) => setUser(userData) + const logout = () => setUser(null) + + return { user, login, logout } +} + +// useUserData.js + +import useFetch from './useFetch' + +function useUserData(userId) { + const { data, loading, error } = useFetch(`/api/users/${userId}`) + return { data, loading, error } +} + +// usePermissions.js +function usePermissions(userRoles = []) { + const hasPermission = (permission) => userRoles.includes(permission) + return { hasPermission } +} + +// Usage in a Component +function AdminDashboard({ userId }) { + const { user, login, logout } = useAuth() + const { data: userData, loading: dataLoading } = useUserData(userId) + const { hasPermission } = usePermissions(userData ? userData.roles : []) + + return ( +
+ {user ? ( +
+ + {dataLoading ?

Loading user data...

: hasPermission('admin') ?

Welcome, Admin {userData.name}

:

Access denied

} +
+ ) : ( + + )} +
+ ) +} +``` + +By modularizing each piece of the authentication system into separate custom hooks, we ensure that each hook is individually testable, reusable, and manageable. This approach keeps the `AdminDashboard` component focused on rendering, with minimal logic. + +### Summary + +Custom hooks provide a powerful way to architect stateful and reusable logic in React applications. By following best practices and focusing on modularity, you can create hooks that are easy to test, maintain, and scale across complex applications. The approach to combining, organizing, and testing these hooks leads to clean, efficient, and high-quality code. diff --git a/Frontend/react/rendering-strategies.md b/Frontend/react/rendering-strategies.md new file mode 100644 index 00000000..656707b2 --- /dev/null +++ b/Frontend/react/rendering-strategies.md @@ -0,0 +1,225 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'React rendering strategies with in-depth coverage of Client-Side Rendering (CSR), Server-Side Rendering (SSR), and Static Site Generation (SSG).' +tags: + - 'react' +title: 'Rendering Strategies in React' +short_title: 'Rendering Strategies' +--- + +Client-Side Rendering (CSR), Server-Side Rendering (SSR), and Static-Site Generation (SSG) are three key rendering strategies in modern web development. Each approach has unique advantages and trade-offs, impacting application performance, SEO, and user experience. + +### 1. Client-Side Rendering (CSR) + +**CSR** is the default rendering approach in React applications, where everything from data fetching to rendering happens in the browser. The server delivers a minimal HTML file with a JavaScript bundle, and React takes over from there, rendering the content on the client's side. + +#### How It Works: + +- The browser downloads the JavaScript bundle, which contains the React code. +- React builds the UI on the client by executing the JavaScript code. +- Data fetching happens after the page loads, potentially leading to a delay before content appears. + +#### Advantages of CSR: + +- **Fast Initial Deployment**: Easier to deploy and manage since there's no server-rendering setup. +- **Rich Interactivity**: Great for SPAs (Single Page Applications) with dynamic, highly interactive UI elements. +- **Simplified Development**: Client-side data fetching and rendering simplify development in many scenarios. + +#### Disadvantages of CSR: + +- **Initial Load Time**: Users may experience a blank page or loading spinner until the JavaScript bundle downloads and renders. +- **SEO Challenges**: Since the HTML is minimal, search engines may struggle to crawl and index content, although some modern crawlers can render JavaScript. +- **Performance**: Large bundles can lead to slow page load times, especially on low-bandwidth connections. + +#### Example in React: + +In CSR, data fetching happens on the client side, typically using hooks like `useEffect`. + +```jsx +import { useState, useEffect } from 'react' + +function UserProfile({ userId }) { + const [user, setUser] = useState(null) + + useEffect(() => { + fetch(`/api/users/${userId}`) + .then((response) => response.json()) + .then((data) => setUser(data)) + }, [userId]) + + return user ?
{user.name}
:
Loading...
+} +``` + +### 2. Server-Side Rendering (SSR) + +**SSR** generates HTML on the server for each request. When the user requests a page, the server processes the JavaScript, fetches any necessary data, and returns a fully-rendered HTML page. React components are rendered to HTML strings on the server and sent to the client, where React "hydrates" (attaches event listeners) to the HTML. + +#### How It Works: + +- The server generates HTML with the initial content and sends it to the client. +- The client receives a fully-rendered HTML page and hydrates it, enabling interactivity. +- Additional JavaScript for further user interactions loads in the background. + +#### Advantages of SSR: + +- **Improved SEO**: The initial HTML page is fully rendered, making it easily crawlable by search engines. +- **Faster Time to Interactive (TTI)**: The user sees the fully-rendered content sooner, as it doesn't rely solely on client-side JavaScript for initial render. +- **Content Accessibility**: Even users on slow networks or with JavaScript disabled can see the initial page content. + +#### Disadvantages of SSR: + +- **Server Load**: Each request requires the server to render the page, increasing server workload, especially with many requests. +- **Complexity**: Requires server infrastructure and additional setup, which can increase complexity. +- **Hydration Time**: The browser still needs to download JavaScript and hydrate the page, which can create a slight delay for interactivity. + +#### Example in Next.js: + +Next.js is a React framework that simplifies SSR. With Next.js, you can use `getServerSideProps` to fetch data and render it on the server. + +```jsx +// pages/profile/[id].js + +import React from 'react' + +export async function getServerSideProps(context) { + const { id } = context.params + const res = await fetch(`https://api.example.com/users/${id}`) + const user = await res.json() + + return { props: { user } } +} + +export default function UserProfile({ user }) { + return
{user.name}
+} +``` + +### 3. Static-Site Generation (SSG) + +**SSG** generates HTML at build time. Unlike SSR, which renders HTML on each request, SSG pre-renders pages as static files and serves them on request. This is ideal for content that doesn't change frequently, as it combines the benefits of SSR with the speed of serving static files. + +#### How It Works: + +- The pages are pre-rendered at build time, creating static HTML files. +- When a user requests a page, the server serves the static HTML directly from a CDN or hosting server. +- Any dynamic content or interactivity can be added client-side, often using JavaScript to fetch data or modify the UI after load. + +#### Advantages of SSG: + +- **Fast Performance**: Since the pages are static files, they load very quickly from a CDN or server. +- **SEO-Friendly**: Like SSR, the static HTML is crawlable by search engines. +- **Low Server Load**: No need to generate HTML per request, reducing server resources. + +#### Disadvantages of SSG: + +- **Less Flexibility**: Pages are generated at build time, so content updates require a new build and deployment. +- **Not Ideal for Highly Dynamic Content**: SSG is less suitable for frequently changing content, as updates won't appear until the next build. +- **Extra Build Time**: Large sites can have long build times if each page needs to be generated statically. + +#### Example in Next.js: + +In Next.js, `getStaticProps` generates static pages at build time. This is perfect for content like blog posts or product pages. + +```jsx +// pages/posts/[id].js + +import React from 'react' + +export async function getStaticProps({ params }) { + const res = await fetch(`https://api.example.com/posts/${params.id}`) + const post = await res.json() + + return { props: { post } } +} + +export async function getStaticPaths() { + const res = await fetch('https://api.example.com/posts') + const posts = await res.json() + + const paths = posts.map((post) => ({ + params: { id: post.id.toString() }, + })) + + return { paths, fallback: false } +} + +export default function Post({ post }) { + return
{post.title}
+} +``` + +### Comparing CSR, SSR, and SSG + +| Feature | CSR (Client-Side Rendering) | SSR (Server-Side Rendering) | SSG (Static-Site Generation) | +| --------------------- | ----------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------- | +| **Data Fetching** | Client-side (after page load) | Server-side (on each request) | Server-side (at build time) | +| **Rendering** | Browser | Server for initial, browser for subsequent interactions | Server at build time, browser for interactions | +| **Best For** | Highly interactive apps, SPAs | SEO-sensitive, frequently updated content | Static content, rarely changing pages | +| **SEO** | Limited SEO (due to initial blank HTML) | Great SEO (initial HTML contains full content) | Great SEO (pre-rendered HTML at build time) | +| **Initial Load Time** | Depends on bundle size, slower initial load | Faster initial load, HTML is pre-rendered | Fastest (serving static files), low latency | +| **Content Freshness** | Real-time updates | Real-time updates | Stale until next build | +| **Hosting Cost** | Lower hosting cost (only needs a static server) | Higher hosting cost (server processes each request) | Lower hosting cost (can use CDN for static files) | + +### Choosing Between CSR, SSR, and SSG + +- **CSR** is best for SPAs or applications with highly interactive interfaces that don't rely heavily on SEO, such as dashboards and internal tools. +- **SSR** is suitable for applications that require both SEO and dynamic content, like e-commerce sites or blogs with frequently updated content. +- **SSG** is ideal for static content that doesn't change often, like documentation sites, blog pages, or marketing landing pages. + +### Combining CSR, SSR, and SSG + +In some cases, applications use a **hybrid approach** to leverage the strengths of each technique. For instance: + +- **Next.js** allows you to use SSG for pages with static content, SSR for dynamic pages, and CSR for client-specific interactions. +- **Incremental Static Regeneration (ISR)** in Next.js enables automatic regeneration of static pages at a specified interval, combining the benefits of SSG and SSR for frequently updated content. + +**Example Hybrid Approach in Next.js**: In this example, we use SSG with ISR for product pages and CSR for interactive features like adding items to a cart. + +```jsx +// pages/product/[id].js + +import { useState } from 'react' + +export async function getStaticProps({ params }) { + const res = await fetch(`https://api.example.com/products/${params.id}`) + const product = await res.json() + + return { props: { product }, revalidate: 60 } // ISR: regenerates every 60 seconds +} + +export async function getStaticPaths() { + const res = await fetch('https://api.example.com/products') + const products = await res.json() + + const paths = products.map((product) => ({ + params: { id: product.id.toString() }, + })) + + return { paths, fallback: 'blocking' } +} + +export default function Product({ product }) { + const [cart, setCart] = useState([]) + + const addToCart = () => { + setCart((prevCart) => [...prevCart, product]) + } + + return ( +
+

{product.name}

+ +
+ ) +} +``` + +- **SSG with ISR** serves the product page with updated data every 60 seconds. +- **CSR** is used for the cart functionality, allowing client-side interactions without reloading the page. + +### Summary + +Choosing between CSR, SSR, and SSG depends on your application's needs for **SEO**, **content freshness**, **interactivity**, and **performance**. In many modern apps, a hybrid approach allows you to take advantage of each strategy where it's most beneficial, creating a fast, SEO-friendly, and interactive experience. Leveraging frameworks like **Next.js** simplifies managing these different rendering methods in a single React application, making it easier to build performant, user-friendly applications. diff --git a/Frontend/react/state-management-strategy.md b/Frontend/react/state-management-strategy.md new file mode 100644 index 00000000..87924acd --- /dev/null +++ b/Frontend/react/state-management-strategy.md @@ -0,0 +1,231 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'Discover State Management strategies, best practices, and when to use each approach for scalable, efficient React applications' +tags: + - 'react' +title: 'State Management Strategy in React' +short_title: 'State Management Strategy' +--- + +State management is a core architectural topic in React, especially as applications grow in complexity. While local component state (using `useState` or `useReducer`) is suitable for small to medium apps, more sophisticated state management strategies become essential as your app scales. + +### Local Component State with Hooks + +React’s native `useState` and `useReducer` are sufficient for managing state at the component level and are efficient for isolated, reusable components. However, challenges arise when dealing with deeply nested or cross-component data dependencies. + +**Example use case** + +Use `useReducer` for managing local form state with multiple dependent fields. + +```js +const initialFormState = { name: '', email: '', password: '' } + +function formReducer(state, action) { + switch (action.type) { + case 'UPDATE_FIELD': + return { ...state, [action.field]: action.value } + case 'RESET': + return initialFormState + default: + return state + } +} + +function SignupForm() { + const [state, dispatch] = useReducer(formReducer, initialFormState) + + const handleChange = (e) => { + dispatch({ + type: 'UPDATE_FIELD', + field: e.target.name, + value: e.target.value, + }) + } + + return ( +
+ + + + +
+ ) +} +``` + +**When to Use Local State**: + +- Isolated components with minimal data dependencies. +- Simple, short-lived UI states, such as form inputs, toggles, or animations. + +### Global State with Context API + +The React Context API is suitable for small to medium global state needs, such as user authentication or theme settings. It’s lightweight but can cause re-rendering issues if used improperly in large applications. + +**Example of Centralized Authentication State** + +```jsx +const AuthContext = React.createContext() + +function AuthProvider({ children }) { + const [user, setUser] = useState(null) + + const login = (userData) => setUser(userData) + const logout = () => setUser(null) + + return {children} +} + +function useAuth() { + return useContext(AuthContext) +} + +// Usage: +function Navbar() { + const { user, logout } = useAuth() + return user ? : +} +``` + +**When to Use Context API**: + +- Lightweight global state, like theme, user, or language settings. +- Avoid for complex or frequently updated data, as it can lead to excessive re-renders. + +### Redux or Zustand for Complex Global State + +Redux is well-suited for applications with highly structured, complex, or cross-cutting state needs. It provides predictable state management via a single store and supports middleware for logging, async actions, and more. Alternatively, **Zustand** is a lightweight state management library that’s simpler to set up and more flexible than Redux. + +**Example of Global Cart Management with Redux Toolkit** + +Using Redux Toolkit, you can simplify Redux by automatically generating action creators and reducers. + +```js +import { createSlice, configureStore } from '@reduxjs/toolkit' + +const cartSlice = createSlice({ + name: 'cart', + initialState: [], + reducers: { + addItem: (state, action) => { + state.push(action.payload) + }, + removeItem: (state, action) => { + return state.filter((item) => item.id !== action.payload) + }, + }, +}) + +const store = configureStore({ reducer: { cart: cartSlice.reducer } }) + +// Actions for dispatching: +export const { addItem, removeItem } = cartSlice.actions +export default store +``` + +**Redux vs. Zustand**: + +- **Redux**: More verbose but provides structure, middleware support, and a strong ecosystem (dev tools, middleware for async actions). +- **Zustand**: Minimal boilerplate, straightforward API, and avoids creating a global Redux-like store by encouraging state encapsulation. + +**When to Use Redux or Zustand**: + +- Cross-cutting data dependencies that multiple components need access to. +- Scenarios that benefit from immutability (Redux) or a reactive, hook-based approach (Zustand). + +### Async Data and Server State with React Query or SWR + +Tools like `React Query` and `SWR` are ideal for handling server data. They help manage caching, re-fetching, and synchronization with server data, which is particularly useful in data-intensive applications. + +**Example use case**: React Query simplifies handling server state by caching data and re-fetching when necessary. It also manages states like loading, error, and refetching automatically. + +```jsx +import { useQuery, QueryClient, QueryClientProvider } from 'react-query' + +const queryClient = new QueryClient() + +function fetchUser(userId) { + return fetch(`/api/user/${userId}`).then((res) => res.json()) +} + +function UserProfile({ userId }) { + const { data, error, isLoading } = useQuery(['user', userId], () => fetchUser(userId), { + staleTime: 5 * 60 * 1000, // Data remains fresh for 5 minutes + }) + + if (isLoading) return + if (error) return + return
User: {data.name}
+} + +// Usage in App: +; + + +``` + +**React Query vs. SWR**: + +- **React Query**: More feature-rich and configurable; supports pagination, optimistic updates, and complex cache invalidation. +- **SWR**: Lightweight with a more declarative approach; suitable for simpler use cases. + +**When to Use React Query or SWR**: + +- Server-side data that needs caching, synchronization, and refresh-on-focus. +- Use React Query for applications with complex server data dependencies and SWR for simpler needs. + +### Combined Approach with Context + React Query + +For scalable applications, a hybrid approach works well, where: + +- Context handles small, rarely-changing global state (like theme or user settings). +- React Query or SWR manages server state (API data). +- Local state and custom hooks organize isolated or ephemeral component-specific state. + +**Example Hybrid Structure**: + +```jsx +const UserContext = React.createContext() + +function AppProvider({ children }) { + const [user, setUser] = useState(null) + return {children} +} + +function useUserData(userId) { + return useQuery(['user', userId], () => fetchUser(userId), { staleTime: 5 * 60 * 1000 }) +} + +function UserComponent() { + const { user, setUser } = useContext(UserContext) + const { data: userData } = useUserData(user.id) + + useEffect(() => { + if (userData) setUser(userData) + }, [userData, setUser]) + + return
Welcome, {user ? user.name : 'Guest'}!
+} + +// Usage: +; + + +``` + +**Benefits of the Combined Approach**: + +- Avoids overloading context with complex state management. +- Improves separation of concerns by delegating responsibilities: local state for UI, context for global app state, and React Query for async/server state. + +### Key Takeaways + +- **Local state** for isolated, ephemeral data. +- **Context API** for lightweight global state that rarely changes. +- **Redux/Zustand** for structured, complex state management across large applications. +- **React Query/SWR** for async data, caching, and server-side synchronization. +- **Combined Approach** for scalable, maintainable architecture. diff --git a/Frontend/react/testing-strategies.md b/Frontend/react/testing-strategies.md new file mode 100644 index 00000000..966faf48 --- /dev/null +++ b/Frontend/react/testing-strategies.md @@ -0,0 +1,243 @@ +--- +authors: + - 'thanh' +date: '2024-10-29' +description: 'React testing with unit, integration, and end-to-end approaches' +tags: + - 'react' + - 'testing' +title: 'Testing Strategies in React' +short_title: 'Testing Strategies' +--- + +Testing is essential for ensuring that your code works as expected, is maintainable, and doesn't introduce bugs with future changes. React testing involves **unit tests, integration tests, and end-to-end (e2e) tests**, each targeting different aspects of your application's functionality. + +### Key Testing Strategies for React Applications + +1. **Unit Testing** with Jest and React Testing Library +2. **Integration Testing** for Component Interactions +3. **End-to-End (E2E) Testing** with Cypress +4. **Snapshot Testing** for UI Consistency +5. **Best Practices for Effective Testing** + +### 1\. Unit Testing with Jest and React Testing Library + +**Unit testing** focuses on testing individual components or functions in isolation, ensuring they work as expected independently of other parts of the application. **Jest** is a popular testing framework for JavaScript that's fast and powerful, while **React Testing Library** provides utilities to interact with and assert on component output based on how a user would interact with it. + +#### Setting Up Jest and React Testing Library + +1. Install Jest and React Testing Library: + +```sh +npm install --save-dev jest @testing-library/react +``` + +1. Add a basic test configuration in your `package.json`: + +```js +{ "scripts": { "test": "jest" } } +``` + +#### Example Unit Test for a Button Component + +Suppose we have a `Button` component that accepts a label and an onClick handler. + +```js +// Button.js +export default function Button({ label, onClick }) { + return +} +``` + +**Unit Test for Button Component:** + +```jsx +// Button.test.js +import { render, screen, fireEvent } from '@testing-library/react' +import Button from './Button' + +test('renders the button with a label', () => { + render( + + ) +} +``` + +**Integration Test for Form Component:** + +```jsx +// Form.test.js +import { render, screen, fireEvent } from '@testing-library/react' +import Form from './Form' + +test('submits form with name and email', () => { + const handleSubmit = jest.fn() + render(
) + + fireEvent.change(screen.getByPlaceholderText('Name'), { target: { value: 'John' } }) + fireEvent.change(screen.getByPlaceholderText('Email'), { target: { value: 'john@example.com' } }) + fireEvent.click(screen.getByText('Submit')) + + expect(handleSubmit).toHaveBeenCalledWith({ name: 'John', email: 'john@example.com' }) +}) +``` + +**Explanation**: + +- We simulate typing into both input fields, then trigger the form submission to ensure `onSubmit` is called with the correct data. + +**Benefits**: + +- **Interaction Testing**: Validates that components interact correctly, ensuring data flows as expected. +- **Form and Input Testing**: Particularly useful for forms and multistep processes, verifying that all parts work in sequence. + +### 3. End-to-End (E2E) Testing with Cypress + +E2E tests simulate real user scenarios, covering the entire flow from start to finish, including interactions with the backend if needed. **Cypress** is a powerful tool for E2E testing in JavaScript applications, allowing for testing of full workflows across pages. + +#### Setting Up Cypress + +1. Install Cypress: + +```sh +npm install --save-dev cypress +``` + +1. Open Cypress for the first time: + +```sh +npx cypress open +``` + +#### Example E2E Test for a Login Flow + +Suppose we have a login form where users enter an email and password to authenticate. + +```jsx +// cypress/integration/login.spec.js +describe('Login Flow', () => { + it('logs in a user with valid credentials', () => { + cy.visit('/login') + cy.get('input[name=email]').type('john@example.com') + cy.get('input[name=password]').type('password123') + cy.get('button[type=submit]').click() + + cy.url().should('include', '/dashboard') + cy.contains('Welcome, John').should('be.visible') + }) +}) +``` + +**Explanation**: + +- **`cy.visit("/login")`** navigates to the login page. +- **Assertions** check that the login was successful by verifying the URL and checking for a welcome message. + +**Benefits**: + +- **Real User Simulation**: Tests full workflows, covering real user interactions with the application. +- **Cross-Page Coverage**: Ensures that transitions between pages work as expected and user data is preserved. + +### 4. Snapshot Testing for UI Consistency + +Snapshot tests capture the current state of a component's output (i.e., its rendered HTML) and compare it to a saved version. Snapshot testing is helpful for detecting unintended changes in the component's visual structure. + +#### Snapshot Testing with Jest + +```jsx +// Header.test.js +import { render } from '@testing-library/react' +import Header from './Header' + +test('renders the header correctly', () => { + const { asFragment } = render(
) + expect(asFragment()).toMatchSnapshot() +}) +``` + +**Explanation**: + +- `asFragment()` captures the component's current rendered state. +- `toMatchSnapshot()` checks the current output against a previously saved snapshot. + +**Benefits**: + +- **UI Consistency**: Ensures that the UI remains visually consistent across updates. +- **Quick Regression Detection**: Quickly identifies changes to the component's structure, ideal for components with complex styles or markup. + +**Limitations**: + +- Snapshots can be too sensitive to minor changes, so they are best used for components with stable layouts or infrequent updates. + +### 5. Best Practices for Effective Testing + +1. **Follow the Testing Pyramid**: Focus primarily on unit tests, followed by integration tests, and finally E2E tests. This balances test coverage with performance and maintainability. + +2. **Test from the User's Perspective**: Use React Testing Library's queries like `getByText`, `getByRole`, and `getByLabelText` to mimic how users interact with your UI. Avoid testing internal implementation details, focusing on behavior instead. + +3. **Avoid Overuse of Snapshot Tests**: Snapshot tests are helpful but can become brittle if overused. Use them selectively for components with complex or static UI. + +4. **Mock External Dependencies**: For unit and integration tests, mock API calls, third-party libraries, and other dependencies to isolate the code under test. Libraries like **msw** (Mock Service Worker) can be used to mock API responses. + +5. **Run Tests in CI/CD**: Automate tests in your CI/CD pipeline to catch bugs early in the development process. Run unit and integration tests for each commit and E2E tests periodically or before release. + +6. **Structure Tests Closely to Source Files**: Place each test file alongside its component or module. This structure makes it easy to locate and update tests when refactoring. + +### Summary + +Incorporating a comprehensive testing strategy helps ensure code quality, user experience, and long-term maintainability. Here's a quick summary: + +- **Unit Testing**: Focus on individual components and functions with Jest and React Testing Library. +- **Integration Testing**: Test multiple components together, ensuring they work in harmony. +- **End-to-End Testing**: Use Cypress to cover full workflows and user journeys, verifying app behavior across pages. +- **Snapshot Testing**: Capture and compare UI structures, helpful for components with complex, static layouts. +- **Best Practices**: Adopt the testing pyramid, test from the user's perspective, mock dependencies, and automate tests in CI/CD