React code examples - rs-hash/Learning GitHub Wiki

1. useState

useState: Allows you to add state to a functional component.

import React, { useState } from "react";

function UseStateComp() {
  const [count, setCount] = useState(0);

  const addCount = () => {
    setCount((prev) => prev + 1);
  };

  const removeCount = () => {
    setCount((prev) => prev - 1);
  };

  return (
    <div>
      <h1>Count is {count}</h1>
      <button onClick={addCount}>Add 1</button>
      <button onClick={removeCount}>Remove 1</button>
    </div>
  );
}

export default UseStateComp;

2. useEffect

useEffect: Enables side effects in functional components, such as data fetching, DOM manipulation, and subscriptions.

useEffect is a React hook that allows you to perform side effects in functional components. Side effects may include data fetching, DOM manipulation, or subscribing to external data sources. useEffect is similar to lifecycle methods in class components, but it's designed for use in functional components. It takes two arguments: a function that contains the code to run for the side effect and an optional array of dependencies that specify when the effect should run.

Here's a code example demonstrating how to use useEffect to fetch data from an API when a component mounts:

import React, { useState, useEffect } from 'react';

function DataFetching() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Define an asynchronous function to fetch data
    async function fetchData() {
      try {
        // Fetch data from an API (e.g., JSONPlaceholder)
        const response = await fetch('https://jsonplaceholder.typicode.com/posts');
        const jsonData = await response.json();

        // Update the component's state with the fetched data
        setData(jsonData);
        setLoading(false); // Set loading to false when data is fetched
      } catch (error) {
        console.error('Error fetching data:', error);
        setLoading(false); // Set loading to false in case of an error
      }
    }

    // Call the fetchData function when the component mounts
    fetchData();
  }, []); // The empty dependency array means this effect runs only once on mount

  return (
    <div>
      <h1>Data Fetching</h1>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {data.map((item) => (
            <li key={item.id}>{item.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default DataFetching;

3. useContext

useContext: Provides access to the context values defined in a higher-level component in the component tree.

useContext and the Context API are features in React that allow you to pass data (such as configuration settings, themes, or user authentication status) down the component tree without manually passing props through every level of nesting. This is particularly useful for managing global state or providing data that many components need access to.

Here's a brief explanation of useContext and the Context API in React:

Context API: The Context API is a part of React that provides a way to share values, such as state or functions, between components without having to explicitly pass them through props. It consists of two main parts: the createContext function and a Context.Provider component. You create a context using createContext, and then you wrap a part of your component tree with a Context.Provider to make the context available to components nested inside it.

useContext Hook: The useContext hook is a React hook that allows functional components to consume values from a context created with createContext. It simplifies accessing context values without needing to wrap components with Context.Consumer or passing props. You simply call useContext with the context object to access the value.

  1. First, ensure you have React and React DOM installed. If not, you can install them using npm or yarn:
npm install react react-dom
  1. Create your React context in a separate file, for example, MyContext.js:
import React, { createContext, useContext, useState } from 'react';

// Create a context object
const MyContext = createContext();

// Create a context provider component
export function MyProvider({ children }) {
  const [name, setName] = useState('John Doe');

  return (
    <MyContext.Provider value={{ name, setName }}>
      {children}
    </MyContext.Provider>
  );
}

// Create a custom hook to access the context
export function useMyContext() {
  return useContext(MyContext);
}
  1. In your main application file, wrap your component tree with the MyProvider:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { MyProvider } from './MyContext'; // Import the context provider

ReactDOM.render(
  <MyProvider>
    <App />
  </MyProvider>,
  document.getElementById('root')
);
  1. Now, in your actual components, you can use the useMyContext hook to access the context:
import React from 'react';
import { useMyContext } from './MyContext'; // Import the custom hook

function MyComponent() {
  const { name, setName } = useMyContext();

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <button onClick={() => setName('New Name')}>Change Name</button>
    </div>
  );
}

export default MyComponent;

4. useRef

useRef is a React Hook that provides a way to interact with DOM elements directly or to store mutable values that persist between renders without causing re-renders. It's commonly used for accessing and manipulating DOM elements and for preserving values that should not trigger component re-renders. Here's a code example to demonstrate how to use useRef:

import React, { useRef, useEffect } from 'react';

function App() {
  // Create a ref for a DOM element
  const myButtonRef = useRef(null);

  // Create a ref for a mutable value
  const countRef = useRef(0);

  // Function to handle a button click
  const handleClick = () => {
    // Access and manipulate the DOM element directly
    if (myButtonRef.current) {
      myButtonRef.current.innerHTML = 'Button Clicked!';
    }

    // Access and update a mutable value
    countRef.current += 1;
    console.log(`Button clicked ${countRef.current} times.`);
  };

  // Use useEffect to run code after render
  useEffect(() => {
    // Focus the button element on mount
    if (myButtonRef.current) {
      myButtonRef.current.focus();
    }
  }, []);

  return (
    <div>
      <h1>useRef Example</h1>
      <button ref={myButtonRef} onClick={handleClick}>
        Click me!
      </button>
      <p>Click count: {countRef.current}</p>
    </div>
  );
}

export default App;

directly updating the countRef.current value won't trigger a re-render of the component to reflect the updated value in the UI because useRef doesn't trigger re-renders when its value changes. To display the updated count in the UI, you should use useState or useReducer to manage the state.

5. useReducer

useReducer is another React Hook that is used for state management, especially when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. It is similar to useState, but it offers more control over state updates, making it suitable for managing more complex state transitions.

Here's how you can use useReducer with a code example:

import React, { useReducer } from 'react';

// Reducer function that takes the current state and an action and returns the new state
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

function Counter() {
  // Initialize state using useReducer
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <h1>Counter</h1>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

export default Counter;

In this example:

  1. We define a reducer function counterReducer that takes the current state and an action object as parameters and returns the new state based on the action type.

  2. Inside the Counter component, we use the useReducer hook to initialize the state. We pass the counterReducer function as the first argument and an initial state object ({ count: 0 }) as the second argument.

  3. In the JSX, we display the current count from the state using state.count. We also have three buttons that dispatch different actions when clicked: "Increment," "Decrement," and "Reset." Each button calls dispatch with an action object containing a type property, which is used by the reducer to determine how to update the state.

When a button is clicked, the counterReducer function is called with the current state and the action, and it returns the new state. React then re-renders the component with the updated state, reflecting the changes in the UI.

useReducer is particularly useful when you have more complex state updates or when the state logic depends on the previous state. It allows you to centralize state transitions and makes it easier to test and reason about state changes in your application.

6. useCallback

useCallback is a React Hook that's used to memoize functions, especially when passing them as props to child components or when they are dependencies for other hooks. It helps optimize performance by preventing unnecessary re-creations of function instances.

While useCallback is used to memoize functions to prevent unnecessary re-creations, if you also want to optimize the rendering of child components, you can use React.memo in conjunction with useCallback. React.memo is a higher-order component (HOC) that memoizes a functional component, preventing it from re-rendering unless its props change.

Here's an updated example with both useCallback and React.memo for better optimization:

ParentComponent.js:

import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <h1>Parent Component</h1>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

export default ParentComponent;

ChildComponent.js:

import React from 'react';

const ChildComponent = React.memo(({ onClick }) => {
  return (
    <div>
      <h2>Child Component</h2>
      <button onClick={onClick}>Increment from Child</button>
    </div>
  );
});

export default ChildComponent;

In this updated example:

  1. ChildComponent is wrapped with React.memo. This memoizes the component so that it won't re-render unless its props change. This is especially useful when you want to optimize the rendering of child components.

  2. The onClick prop is passed to ChildComponent, and when the "Increment from Child" button is clicked, it calls the onClick prop, which is the memoized handleClick function from the parent component.

By using both useCallback in the parent component and React.memo in the child component, you optimize performance by avoiding unnecessary re-renders of the child component when its props haven't changed. This combination is particularly effective in scenarios where you have a large number of child components or complex rendering logic.

how useCallback Memoizes

useCallback is a React Hook that memoizes functions, and it plays a crucial role in preventing unnecessary re-creations of function instances, especially when used in conjunction with child components. To understand how useCallback accomplishes this, let's break down the process step by step:

  1. The Problem of Function Re-Creation:

    In a typical React component, functions declared inside the component are created anew on every render. Consider this example:

    import React, { useState } from 'react';
    
    function ParentComponent() {
      const [count, setCount] = useState(0);
    
      // This function is re-created on every render
      const handleClick = () => {
        setCount(count + 1);
      };
    
      return (
        <div>
          <h1>Parent Component</h1>
          <p>Count: {count}</p>
          <button onClick={handleClick}>Increment</button>
        </div>
      );
    }

    In this example, the handleClick function is re-created every time ParentComponent renders, even if count hasn't changed. This is because JavaScript creates a new function instance each time the component is re-rendered.

  2. The Role of useCallback:

    useCallback is used to memoize functions, which means it remembers the function instance between renders unless its dependencies change. It takes two arguments:

    • The first argument is the function you want to memoize.
    • The second argument is an array of dependencies. If any of these dependencies change between renders, the function is re-created; otherwise, it's reused.

    Here's how useCallback can be used to prevent unnecessary function re-creation:

    import React, { useState, useCallback } from 'react';
    
    function ParentComponent() {
      const [count, setCount] = useState(0);
    
      // Memoize the function using useCallback
      const handleClick = useCallback(() => {
        setCount(count + 1);
      }, [count]); // The dependency array includes 'count'
    
      return (
        <div>
          <h1>Parent Component</h1>
          <p>Count: {count}</p>
          <button onClick={handleClick}>Increment</button>
        </div>
      );
    }

    In this updated example, handleClick is wrapped in useCallback, and the dependency array [count] is provided. Now, handleClick will only be recreated if count changes. If count remains the same between renders, the same function instance is reused.

  3. Child Components and useCallback:

    When you pass a memoized function like handleClick to child components, you're ensuring that the child components receive the same function instance across renders, as long as the dependencies haven't changed.

    This is important because if you didn't use useCallback, every time the parent component re-renders, it would create a new handleClick function, and if you passed this new function to child components, it would cause those child components to re-render unnecessarily, even if the function logic hasn't changed.

    By using useCallback, you optimize the performance of your application by preventing these unnecessary re-renders in child components. The child components don't re-render unless the actual dependencies (in this case, count) change, allowing for more efficient updates.

7. useMemo

useMemo is a React Hook that is used to memoize the result of a computation. Memoization is an optimization technique where you store the result of a function call and return the cached result when the same inputs occur again. This can help prevent unnecessary re-computation of values, especially when the computation is expensive or when you want to optimize the performance of your React components.

The useMemo hook takes two arguments:

  1. A function that computes the memoized value.
  2. An array of dependencies.

The function is executed during the rendering phase, and its result is cached. The cached result is returned whenever the component re-renders as long as none of the dependencies in the dependency array have changed. If any of the dependencies have changed since the last render, the function is re-executed, and the new result is cached.

Here's the basic syntax of useMemo:

const memoizedValue = useMemo(() => {
  // computation or value to be memoized
}, [dependency1, dependency2, ...]);
  • memoizedValue: The computed and memoized value.
  • () => {...}: The function that computes the memoized value.
  • [dependency1, dependency2, ...]: An array of dependencies that trigger the recomputation of the memoized value if any of these dependencies change.

example of useMemo where we calculate the square of a number and memoize the result:

import React, { useState, useMemo } from 'react';

function App() {
  const [number, setNumber] = useState(1);

  // Calculate the square of the number using useMemo
  const squaredNumber = useMemo(() => {
    console.log(`Calculating square of ${number}...`); // Log when the computation happens
    return number * number;
  }, [number]); // The dependency array specifies when the value should be recomputed

  const handleNumberChange = (e) => {
    const newNumber = parseInt(e.target.value, 10);
    setNumber(newNumber);
  };

  return (
    <div>
      <h1>useMemo Example</h1>
      <label>
        Enter a number:
        <input type="number" value={number} onChange={handleNumberChange} />
      </label>
      <p>Square of {number} is: {squaredNumber}</p>
    </div>
  );
}

export default App;

8. Create a class component with state and lifecycle methods.

Here's an example of a class component in React that includes state and lifecycle methods:

import React, { Component } from 'react';

class ClassComponent extends Component {
  constructor(props) {
    super(props);

    // Initialize state
    this.state = {
      count: 0,
    };
  }

  // Lifecycle method: componentDidMount
  componentDidMount() {
    console.log('Component is mounted.');
  }

  // Lifecycle method: componentDidUpdate
  componentDidUpdate(prevProps, prevState) {
    console.log('Component updated.');
    console.log('Previous state:', prevState);
    console.log('Current state:', this.state);
  }

  // Lifecycle method: componentWillUnmount
  componentWillUnmount() {
    console.log('Component will unmount.');
  }

  // Custom method to increment the count
  incrementCount = () => {
    this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
  };

  render() {
    return (
      <div>
        <h1>Class Component with State and Lifecycle Methods</h1>
        <p>Count: {this.state.count}</p>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

export default ClassComponent;

In this class component:

  • We create a class ClassComponent that extends Component, the base class for class components in React.

  • In the constructor, we initialize the component's state with an initial count of 0.

  • We implement several lifecycle methods:

    • componentDidMount: Called after the component has been mounted (i.e., added to the DOM).
    • componentDidUpdate: Called after the component has been updated (i.e., after a state change or prop change).
    • componentWillUnmount: Called before the component is unmounted (i.e., removed from the DOM).
  • We define a custom method incrementCount that updates the count state when a button is clicked.

  • In the render method, we display the current count and a button to increment it.

This class component demonstrates the use of state, lifecycle methods, and a custom method to manage and display state within a React component.

9. Props and State:

Props and State in React:

Props and state are fundamental concepts in React for managing data and communication between components. They serve different purposes:

  1. Props (Properties):

    • Props are read-only: They are used to pass data from parent components (or the parent component's state) to child components. Props are immutable, meaning they cannot be modified by the child component that receives them.

    • Props are external: They allow data to flow from top-down (from parent to child). They help in making components reusable and composable since child components can receive data from their parent components.

    • Props are used for configuration: You pass props to a component to configure its behavior or to provide data that it needs to display.

  2. State:

    • State is mutable: State is used to manage and store data that can change over time within a component. Unlike props, components can modify their own state using the setState method.

    • State is internal: It is local to the component where it is defined. Other components cannot directly access or modify another component's state.

    • State is used for interactivity: You use state to manage the internal state of a component. For example, you might use state to track form input, toggle a menu, or manage a counter.

Building a Component that Receives Props and Updates State: Certainly! Here's an example of a functional component in React that receives props and updates its state based on user interactions:

import React, { useState } from 'react';

function FunctionalComponent({ someProp }) {
  // Initialize state
  const [count, setCount] = useState(0);

  // Custom function to increment the count in state
  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h1>FunctionalComponent</h1>
      <p>Received Prop: {someProp}</p>
      <p>Current Count: {count}</p>
      <button onClick={incrementCount}>Increment Count</button>
    </div>
  );
}

export default FunctionalComponent;

10. Component Communication:

Implement parent-child communication using props.

In React, parent-child communication using props is a fundamental way to pass data from a parent component to a child component. Here's an example of how you can achieve parent-child communication using props in functional components:

ParentComponent.js:

import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [message, setMessage] = useState('Hello from Parent!');

  const handleMessageChange = () => {
    setMessage('Updated message from Parent!');
  };

  return (
    <div>
      <h1>Parent Component</h1>
      <p>Message: {message}</p>
      <button onClick={handleMessageChange}>Change Message</button>
      <ChildComponent message={message} />
    </div>
  );
}

export default ParentComponent;

ChildComponent.js:

import React from 'react';

function ChildComponent({ message }) {
  return (
    <div>
      <h2>Child Component</h2>
      <p>Received Message: {message}</p>
    </div>
  );
}

export default ChildComponent;

11. Hooks:

Convert a class component to a functional component using hooks.

Sure, let's convert a class component to a functional component using hooks. Here's an example:

Class Component (Before Conversion):

import React, { Component } from 'react';

class CounterClassComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  incrementCount = () => {
    this.setState((prevState) => ({
      count: prevState.count + 1,
    }));
  };

  render() {
    return (
      <div>
        <h1>Counter (Class Component)</h1>
        <p>Count: {this.state.count}</p>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

export default CounterClassComponent;

Functional Component with Hooks (After Conversion):

import React, { useState } from 'react';

function CounterFunctionalComponent() {
  // Initialize state using the useState hook
  const [count, setCount] = useState(0);

  // Define a function to increment the count
  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      <h1>Counter (Functional Component with Hooks)</h1>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
}

export default CounterFunctionalComponent;

12. Conditional Rendering:

You can conditionally render a functional component in React based on a boolean value or user authentication by using a simple JavaScript conditional statement within your JSX. Here's an example:

import React from 'react';

function AuthenticatedComponent() {
  return <p>Welcome, authenticated user!</p>;
}

function UnauthenticatedComponent() {
  return <p>Please log in to access this content.</p>;
}

function App() {
  const isAuthenticated = true; // Replace with your authentication logic

  return (
    <div>
      <h1>Conditional Rendering Example</h1>
      {isAuthenticated ? <AuthenticatedComponent /> : <UnauthenticatedComponent />}
    </div>
  );
}

export default App;

13. Mapping and Lists:

Render a list of items using map and unique keys.

Add delete functionality to remove items from a list.

You can render a list of items using the map function and unique keys in a functional component in React. To add delete functionality to remove items from the list, you can create a delete button for each item and implement an event handler to handle the item removal. Here's an example:

import React, { useState } from 'react';

function ItemList() {
  const [items, setItems] = useState([
    { id: 1, text: 'Item 1' },
    { id: 2, text: 'Item 2' },
    { id: 3, text: 'Item 3' },
  ]);

  const handleDelete = (itemId) => {
    // Filter out the item with the given itemId
    const updatedItems = items.filter((item) => item.id !== itemId);
    setItems(updatedItems);
  };

  return (
    <div>
      <h1>Item List</h1>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            {item.text}
            <button onClick={() => handleDelete(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ItemList;

14. Forms and Controlled Components:

Create a controlled form component that updates state as the user types.

You can create a controlled functional form component in React that updates its state as the user types by using the useState hook to manage the form input's value and the onChange event handler to update the state. Here's an example:

import React, { useState } from 'react';

function ControlledForm() {
  // Initialize state for the form input
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
  });

  // Event handler to update form data
  const handleInputChange = (event) => {
    const { name, value } = event.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };

  return (
    <div>
      <h1>Controlled Form</h1>
      <form>
        <div>
          <label htmlFor="firstName">First Name:</label>
          <input
            type="text"
            id="firstName"
            name="firstName"
            value={formData.firstName}
            onChange={handleInputChange}
          />
        </div>
        <div>
          <label htmlFor="lastName">Last Name:</label>
          <input
            type="text"
            id="lastName"
            name="lastName"
            value={formData.lastName}
            onChange={handleInputChange}
          />
        </div>
      </form>
      <div>
        <p>First Name: {formData.firstName}</p>
        <p>Last Name: {formData.lastName}</p>
      </div>
    </div>
  );
}

export default ControlledForm;

Validate form input and show error messages.

To validate form input in a functional component and display error messages, you can add validation logic to your handleInputChange function and conditionally render error messages based on the validation results. Here's an example of how to do that:

import React, { useState } from 'react';

function ControlledFormWithValidation() {
  // Initialize state for the form input and error messages
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
  });

  const [errors, setErrors] = useState({
    firstName: '',
    lastName: '',
  });

  // Event handler to update form data and validate
  const handleInputChange = (event) => {
    const { name, value } = event.target;
    setFormData({
      ...formData,
      [name]: value,
    });

    // Validation logic (you can customize this as needed)
    if (name === 'firstName') {
      if (value.length < 2) {
        setErrors({
          ...errors,
          firstName: 'First name must be at least 2 characters long.',
        });
      } else {
        setErrors({
          ...errors,
          firstName: '',
        });
      }
    } else if (name === 'lastName') {
      if (value.length < 2) {
        setErrors({
          ...errors,
          lastName: 'Last name must be at least 2 characters long.',
        });
      } else {
        setErrors({
          ...errors,
          lastName: '',
        });
      }
    }
  };

  return (
    <div>
      <h1>Controlled Form with Validation</h1>
      <form>
        <div>
          <label htmlFor="firstName">First Name:</label>
          <input
            type="text"
            id="firstName"
            name="firstName"
            value={formData.firstName}
            onChange={handleInputChange}
          />
          {errors.firstName && <p className="error">{errors.firstName}</p>}
        </div>
        <div>
          <label htmlFor="lastName">Last Name:</label>
          <input
            type="text"
            id="lastName"
            name="lastName"
            value={formData.lastName}
            onChange={handleInputChange}
          />
          {errors.lastName && <p className="error">{errors.lastName}</p>}
        </div>
      </form>
    </div>
  );
}

export default ControlledFormWithValidation;

15. Event Handling:

Handle events like clicks, input changes, and form submissions.

In React, you can handle various events like clicks, input changes, and form submissions by defining event handlers. Here are examples for each of these event types:

  1. Click Events:

    To handle click events, you can use the onClick attribute on an HTML element like a button or a link. Here's an example of handling a click event:

    import React from 'react';
    
    function ClickEventExample() {
      const handleClick = () => {
        alert('Button clicked!');
      };
    
      return (
        <div>
          <h1>Click Event Example</h1>
          <button onClick={handleClick}>Click me</button>
        </div>
      );
    }
    
    export default ClickEventExample;

    In this example, when the "Click me" button is clicked, the handleClick function is executed, and an alert is shown.

  2. Input Change Events:

    To handle input changes, you can use the onChange attribute on an input element. Here's an example:

    import React, { useState } from 'react';
    
    function InputChangeExample() {
      const [inputValue, setInputValue] = useState('');
    
      const handleInputChange = (event) => {
        setInputValue(event.target.value);
      };
    
      return (
        <div>
          <h1>Input Change Event Example</h1>
          <input
            type="text"
            value={inputValue}
            onChange={handleInputChange}
            placeholder="Type something..."
          />
          <p>You typed: {inputValue}</p>
        </div>
      );
    }
    
    export default InputChangeExample;

    In this example, as the user types into the input field, the handleInputChange function updates the inputValue state, and the typed text is displayed.

  3. Form Submission Events:

    To handle form submissions, you can use the onSubmit attribute on a <form> element. Here's an example:

    import React, { useState } from 'react';
    
    function FormSubmitExample() {
      const [formData, setFormData] = useState({
        username: '',
        email: '',
      });
    
      const handleSubmit = (event) => {
        event.preventDefault();
        // Perform form submission logic (e.g., send data to the server)
        console.log('Form submitted with data:', formData);
      };
    
      const handleInputChange = (event) => {
        const { name, value } = event.target;
        setFormData({
          ...formData,
          [name]: value,
        });
      };
    
      return (
        <div>
          <h1>Form Submission Event Example</h1>
          <form onSubmit={handleSubmit}>
            <label htmlFor="username">Username:</label>
            <input
              type="text"
              id="username"
              name="username"
              value={formData.username}
              onChange={handleInputChange}
            />
            <label htmlFor="email">Email:</label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleInputChange}
            />
            <button type="submit">Submit</button>
          </form>
        </div>
      );
    }
    
    export default FormSubmitExample;

    In this example, when the form is submitted, the handleSubmit function is called, and we prevent the default form submission behavior using event.preventDefault(). The form data is then logged to the console, but you can replace that with your submission logic.

These examples demonstrate how to handle click events, input change events, and form submission events in React components. You can customize the event handlers to perform specific actions based on your application's requirements.

Demonstrate event delegation and event bubbling.

Event delegation and event bubbling are concepts that are not specific to React but are part of the DOM event handling mechanism. React utilizes these concepts as well because it's built on top of the DOM. Let's look at how event delegation and event bubbling work in a React context.

Event Bubbling:

Event bubbling is a mechanism in which an event triggered on a nested DOM element will propagate up the DOM tree, triggering the same event on its ancestors. In React, you can handle bubbling events by defining event handlers on parent components and allowing the events to propagate up to them. Here's an example:

import React from 'react';

function ParentComponent() {
  const handleParentClick = () => {
    alert('Parent Component Clicked');
  };

  return (
    <div onClick={handleParentClick} style={{ border: '1px solid #ccc', padding: '20px' }}>
      <h1>Parent Component</h1>
      <ChildComponent />
    </div>
  );
}

function ChildComponent() {
  const handleChildClick = () => {
    alert('Child Component Clicked');
  };

  return (
    <div onClick={handleChildClick} style={{ border: '1px solid #aaa', padding: '10px' }}>
      <h2>Child Component</h2>
      <button>Click Me (Child)</button>
    </div>
  );
}

export default ParentComponent;

Event Delegation:

Event delegation is a technique where you attach a single event listener to a common ancestor element of multiple child elements instead of attaching individual event listeners to each child element. In React, this can be achieved by handling events in a parent component and using the event.target property to determine which child element was the actual target of the event. Here's an example:

import React from 'react';

function ParentComponent() {
  const handleButtonClick = (event) => {
    if (event.target.tagName === 'BUTTON') {
      alert('Button clicked: ' + event.target.textContent);
    }
  };

  return (
    <div onClick={handleButtonClick}>
      <h1>Parent Component</h1>
      <button>Click Me 1</button>
      <button>Click Me 2</button>
    </div>
  );
}

export default ParentComponent;

16. Routing:

Set up routing using a library like React Router.

Setting up routing in a React application can be done using libraries like React Router. React Router allows you to define multiple "routes" in your application, each corresponding to a different URL or route, and it handles rendering the appropriate components for each route. Here's an example of setting up routing using React Router:

First, you need to install React Router if you haven't already. You can do this using npm or yarn:

npm install react-router-dom
# or
yarn add react-router-dom

Now, let's create a basic example with three routes: a home page, an about page, and a contact page.

App.js:

import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ContactPage from './ContactPage';

function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/contact">Contact</Link>
            </li>
          </ul>
        </nav>

        <hr />
         <Routes>
            <Route exact path="/" element={<HomePage/>}></Route>
            <Route path="/Parent" element={<AboutPage/>}></Route>
            <Route path="/Controlled" element={<ContactPage/>}></Route>
          </Routes>
      </div>
    </Router>
  );
}

export default App;

17. Redux or State Management:

Integrate Redux or another state management library

  • Redux is a library for managing state using same techniques as useReducer
  • useReducer has single reducer function vs Redux has multiple Reducer function
  • Redux store -> Dispatch fn -> multiple Reducer -> State
  • In Redux, if we want to change state in any way, dispatch is called
  • Redux toolkit is a wrapper around plain redux
  • Redux toolkit simplifies the action type creation process

Redux ToolKit

Sure, here's a simple example of using Redux Toolkit in a React project, broken down into steps:

Step 1: Set Up Your React Application

Create a new React application using Create React App or your preferred setup method:

npx create-react-app redux-toolkit-example
cd redux-toolkit-example

Step 2: Install Dependencies

Install Redux Toolkit, React Redux, and Redux DevTools Extension:

npm install @reduxjs/toolkit react-redux

Step 3: Create Your Redux Store and Slice

In Redux Toolkit, you'll define slices that contain both the reducer and action creators for a specific piece of state. Let's create a simple counter slice.

src/counterSlice.js:

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
    decrement: (state) => state - 1,
  },
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

Step 4: Create the Redux Store

Configure your Redux store using configureStore from Redux Toolkit and include your counter reducer:

src/store.js:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

Step 5: Create Your React Component

Create a simple React component that uses the Redux store to display and update the counter.

src/App.js:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function App() {
  const counter = useSelector((state) => state.counter);
  const dispatch = useDispatch();

  return (
    <div className="App">
      <h1>Counter: {counter}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

export default App;

Step 6: Connect Your App to the Redux Store

Wrap your application with the Provider component from react-redux to make the Redux store available to your components.

src/index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Step 7: Run Your Application

Start your application using npm start or yarn start. You should see a simple counter app that allows you to increment and decrement the counter using Redux Toolkit.

That's it! You've successfully set up a simple React application with Redux Toolkit. This example demonstrates the core steps of using Redux Toolkit, but you can expand on this foundation by adding more slices, reducers, and actions to manage your application's state.

Classic Redux

Here's a simple example of using classic Redux in a React project, broken down into steps:

Step 1: Set Up Your React Application

Create a new React application using Create React App or your preferred setup method:

npx create-react-app classic-redux-example
cd classic-redux-example

Step 2: Install Dependencies

Install Redux and React Redux:

npm install redux react-redux

Step 3: Create Redux Actions and Reducers

In classic Redux, you'll define actions and reducers separately. Let's create a simple counter example.

src/actions.js:

// Define action types
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

// Action creators
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

src/reducers.js:

import { INCREMENT, DECREMENT } from './actions';

// Initial state
const initialState = {
  count: 0,
};

// Reducer function
const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;

Step 4: Create the Redux Store

Combine your reducers and create the Redux store:

src/store.js:

import { createStore } from 'redux';
import counterReducer from './reducers';

const store = createStore(counterReducer);

export default store;

Step 5: Create Your React Component

Create a React component that uses the Redux store to display and update the counter.

src/App.js:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

function App() {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div className="App">
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

export default App;

Step 6: Connect Your App to the Redux Store

Wrap your application with the Provider component from react-redux to make the Redux store available to your components.

src/index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Step 7: Run Your Application

Start your application using npm start or yarn start. You should see a simple counter app that allows you to increment and decrement the counter using classic Redux.

Difference between classic Redux and Redux toolkit

Classic Redux and Redux Toolkit both serve the purpose of state management in a React application, but they have different approaches and levels of abstraction. Here are the key differences between them with examples:

1. Boilerplate Code:

  • Classic Redux: In classic Redux, you need to write a significant amount of boilerplate code to set up your actions, reducers, store configuration, and connect components to the store. Actions and reducers are often defined in separate files, leading to additional imports and more code to maintain.

  • Redux Toolkit: Redux Toolkit significantly reduces boilerplate code. It provides utility functions like createSlice and configureStore that streamline the process of defining actions, reducers, and creating the store. Actions and reducers are often colocated within a single slice, making code more concise and maintainable.

Classic Redux Example:

// actions.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

// reducers.js
const initialState = { count: 0 };

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

Redux Toolkit Example:

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
  },
});

2. Immutability:

  • Classic Redux: In classic Redux, you must ensure immutability when updating the state in reducers. Typically, you create new objects or arrays to represent the updated state.

  • Redux Toolkit: Redux Toolkit simplifies immutability by allowing you to directly mutate the state within reducers. It uses the Immer library under the hood to handle immutability in a more intuitive way.

3. Store Configuration:

  • Classic Redux: In classic Redux, you configure the store manually and add middleware, such as Redux Thunk, for handling asynchronous actions.

  • Redux Toolkit: Redux Toolkit's configureStore function simplifies store configuration. It comes preconfigured with commonly used middleware, including Redux Thunk, making it easier to handle asynchronous actions.

4. Redux DevTools:

  • Classic Redux: You need to configure and enable Redux DevTools Extension separately in classic Redux.

  • Redux Toolkit: Redux Toolkit integrates seamlessly with Redux DevTools Extension, and you don't need to set it up separately.

5. Testing:

  • Classic Redux: Testing reducers and actions in classic Redux can be straightforward, but it requires additional setup and mocking.

  • Redux Toolkit: Redux Toolkit provides utilities for testing your slices and actions, making it easier to write unit tests.

6. Migration:

  • Classic Redux: If you have an existing codebase using classic Redux, migrating to Redux Toolkit can be a manual process and may require refactoring.

  • Redux Toolkit: Redux Toolkit offers tools and guides for migrating from classic Redux to Redux Toolkit, making the transition smoother.

In summary, Redux Toolkit is designed to simplify and improve the developer experience when working with Redux. It reduces boilerplate code, encourages best practices, and provides a more intuitive API for defining actions and reducers. While classic Redux offers more flexibility and control, Redux Toolkit is an excellent choice for most projects due to its productivity benefits.

React Versions

React latest version - React 18 ( March 2022 ) React-Redux latest version - 8.1.2 Redux toolkit latest version - 1.9.5 Redux Toolkit was first introduced on September 22, 2019

Steps to update to React 18

  1. Import React 18 and react dom
  2. import ReactDOM from 'react-dom' to 'react-dom/client'
  3. ReactDOM.render() -> ReactDOM.createRoot(), root.render()

React 18 new features

1. Concurrency - state update prioritization

- useTransition() - > hook
- startTransition() -> class components
- useDeferredValue - > use when we dont have control over state
const [isPending, startTransition] = useTransition()

function updateFilterHandler(event){
   startTransition(()=>{
       setFilterTerm(event.target.value) // lower priority
   })
}

isPending // used for fallback scenario

import { useDeferredValue } from "react";

function productList({pendingData}){

 const deferredProducts = useDeferredValue(pendingData)

return(
   {deferredProducts.map(item=> <li>{item}</li>)}
)
}

2. New hooks for Library authors

- useSyncExternalStore()
- useInsertionEffect()
- useId() - generates Random unique ID 

3. State Batching - grouping multiple state updating calls together and calling as one update call

- It exisited in React 17 but it happened only inside synchronous function calls and now it's available for async calls - promise, setTimeout

4. Suspense

- can be used to wrap around lazy loaded components
- enables suspense for server side rendering

Implement actions, reducers, and selectors.

Thunk

Using Redux Thunk with Redux Toolkit allows you to handle asynchronous actions in your Redux store. Here's a simple step-by-step example of using Redux Toolkit with Thunk:

Step 1: Set Up Your React Application

Create a new React application using Create React App or your preferred setup method:

npx create-react-app redux-toolkit-thunk-example
cd redux-toolkit-thunk-example

Step 2: Install Dependencies

Install Redux Toolkit, React Redux, and Redux Thunk:

npm install @reduxjs/toolkit react-redux redux-thunk

Step 3: Create Redux Slice

In Redux Toolkit, let's create a slice to manage a list of items. We'll also define a Thunk action for fetching data asynchronously.

src/itemsSlice.js:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Async Thunk action for fetching items
export const fetchItems = createAsyncThunk('items/fetchItems', async () => {
  // Simulate an API call
  const response = await fetch('https://jsonplaceholder.typicode.com/todos');
  const data = await response.json();
  return data;
});

const itemsSlice = createSlice({
  name: 'items',
  initialState: {
    items: [],
    loading: false,
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchItems.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchItems.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchItems.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export default itemsSlice.reducer;

Step 4: Create the Redux Store

Configure your Redux store, including middleware for Redux Thunk:

src/store.js:

import { configureStore } from '@reduxjs/toolkit';
import itemsReducer from './itemsSlice';
import thunk from 'redux-thunk';

const store = configureStore({
  reducer: {
    items: itemsReducer,
  },
  middleware: [thunk],
});

export default store;

Step 5: Create Your React Component

Create a React component that uses the Redux store to fetch and display items.

src/App.js:

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchItems } from './itemsSlice';

function App() {
  const items = useSelector((state) => state.items.items);
  const loading = useSelector((state) => state.items.loading);
  const error = useSelector((state) => state.items.error);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchItems());
  }, [dispatch]);

  return (
    <div className="App">
      {loading ? (
        <p>Loading...</p>
      ) : error ? (
        <p>Error: {error}</p>
      ) : (
        <ul>
          {items.map((item) => (
            <li key={item.id}>{item.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default App;

Step 6: Connect Your App to the Redux Store

Wrap your application with the Provider component from react-redux to make the Redux store available to your components.

src/index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Step 7: Run Your Application

Start your application using npm start or yarn start. You should see a simple app that fetches and displays a list of items from a mock API using Redux Toolkit and Redux Thunk.

SAGA

Step 1: Create a Redux Toolkit Slice

In your Redux Toolkit setup, create a slice using createSlice to define your actions and reducer.

// dataSlice.js
import { createSlice } from '@reduxjs/toolkit';

const dataSlice = createSlice({
  name: 'data',
  initialState: {
    data: null,
    error: null,
  },
  reducers: {
    fetchDataSuccess: (state, action) => {
      state.data = action.payload;
      state.error = null;
    },
    fetchDataFailure: (state, action) => {
      state.data = null;
      state.error = action.payload;
    },
  },
});

export const { fetchDataSuccess, fetchDataFailure } = dataSlice.actions;
export default dataSlice.reducer;

Step 2: Create a Saga File

In a separate file, create a saga that will handle asynchronous data fetching. We'll call it dataSaga.js.

// dataSaga.js
import { put, takeLatest, call } from 'redux-saga/effects';
import { fetchDataSuccess, fetchDataFailure } from './dataSlice';

// Simulated API call function
const fetchDataFromAPI = () => {
  // Replace this with your actual API call
  return fetch('https://api.example.com/data')
    .then((response) => response.json())
    .catch((error) => {
      throw error;
    });
};

// Worker Saga
function* fetchData() {
  try {
    const data = yield call(fetchDataFromAPI);
    yield put(fetchDataSuccess(data));
  } catch (error) {
    yield put(fetchDataFailure(error));
  }
}

// Watcher Saga
export function* watchFetchData() {
  yield takeLatest('data/fetchDataRequest', fetchData);
}

Step 3: Create a Redux Store with Redux Toolkit and Saga Middleware

In your store.js file, create and configure the Redux store with Redux Toolkit and the Redux Saga middleware.

// store.js
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import dataReducer from './dataSlice';
import { watchFetchData } from './dataSaga';

// Create the Redux Saga middleware
const sagaMiddleware = createSagaMiddleware();

// Create the Redux store
const store = configureStore({
  reducer: {
    data: dataReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(sagaMiddleware),
});

// Run the root saga
sagaMiddleware.run(watchFetchData);

export default store;

Step 4: Dispatch the Action in Your Component

In your React component, dispatch the fetchDataRequest action when you want to initiate the data fetching process.

// YourComponent.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchDataRequest } from './dataSlice';

function YourComponent() {
  const dispatch = useDispatch();
  const { data, error } = useSelector((state) => state.data);

  useEffect(() => {
    // Dispatch the action to fetch data when the component mounts
    dispatch(fetchDataRequest());
  }, [dispatch]);

  // Render your component and display the data/error as needed
}

export default YourComponent;

With these steps, you've added Redux Saga to your Redux Toolkit application. The saga watchFetchData will listen for data/fetchDataRequest actions and trigger the fetchData saga to handle the asynchronous data fetching. Remember to replace the example API call with your actual data-fetching logic.

18. Higher-Order Components (HOCs):

Create a higher-order component for reusable logic. Explain the benefits and use cases of HOCs.

Higher-Order Component (HOC) is a design pattern in React that allows you to reuse component logic. It's not a component itself but rather a function that takes a component and returns a new component with enhanced functionality. HOCs are a way to share behavior between components without duplicating code.

Here's a simple example of a Higher-Order Functional Component for reusable logic:

Sure, here's a simple example of implementing user authentication logic using a functional Higher-Order Component (HOC) in React:

import React, { useState } from 'react';

// Simulated authentication logic
const fakeAuthService = {
  isAuthenticated: false,
  login: () => {
    fakeAuthService.isAuthenticated = true;
  },
  logout: () => {
    fakeAuthService.isAuthenticated = false;
  },
};

// Define a Higher-Order Component (HOC) function
const withAuth = (WrappedComponent) => {
  // This HOC adds authentication-related props to the wrapped component
  return function WithAuth(props) {
    const [authenticated, setAuthenticated] = useState(fakeAuthService.isAuthenticated);

    const login = () => {
      fakeAuthService.login();
      setAuthenticated(true);
    };

    const logout = () => {
      fakeAuthService.logout();
      setAuthenticated(false);
    };

    // Pass authentication-related props to the wrapped component
    return (
      <WrappedComponent
        isAuthenticated={authenticated}
        login={login}
        logout={logout}
        {...props}
      />
    );
  };
};

// Create a component that requires authentication
const AuthenticatedComponent = ({ isAuthenticated, login, logout }) => {
  return (
    <div>
      <p>Welcome to the authenticated component!</p>
      <p>Authenticated: {isAuthenticated ? 'Yes' : 'No'}</p>
      {isAuthenticated ? (
        <button onClick={logout}>Logout</button>
      ) : (
        <button onClick={login}>Login</button>
      )}
    </div>
  );
};

// Wrap AuthenticatedComponent with the withAuth HOC
const AuthComponentWithAuth = withAuth(AuthenticatedComponent);

// App component
const App = () => {
  return (
    <div>
      <h1>Authentication Example</h1>
      <AuthComponentWithAuth />
    </div>
  );
};

export default App;

In this example:

  • We have a simulated authentication service (fakeAuthService) with login and logout functions.

  • The withAuth HOC takes a WrappedComponent and adds authentication-related props (isAuthenticated, login, and logout) to it.

  • The AuthenticatedComponent displays the user's authentication status and provides buttons for login and logout. The behavior of the buttons depends on the authentication status.

  • We wrap the AuthenticatedComponent with the withAuth HOC to create AuthComponentWithAuth.

  • In the App component, we render AuthComponentWithAuth to demonstrate how components wrapped with the withAuth HOC can access authentication-related props.

When you run this code, you'll see a simple authentication example where the user can log in and out, and the authentication status is passed as a prop to the wrapped component.

simple HOC example

 // Define an HOC function
  const withColor = (WrappedComponent, color) => {
    // This HOC adds a color prop to the wrapped component
    return (props) => <WrappedComponent {...props} color={color} />;
  };

  // Create a functional component to be wrapped
  const TextComponent = ({ text, color }) => (
    <div style={{ color }}>{text}</div>
  );

  // Wrap TextComponent with the withColor HOC
  const TextWithColor = withColor(TextComponent, "blue");

 <TextWithColor text="Colored text"></TextWithColor> 

20. Error Handling:

Implement error boundaries to catch and handle errors in React components. Discuss best practices for error handling.

Error boundaries in React can also be implemented in functional components using the useEffect and useState hooks. In this example, we'll create a functional error boundary and discuss best practices for error handling.

Implementing Error Boundaries in Functional Components:

import React, { useState, useEffect } from 'react';

// Functional error boundary component
const ErrorBoundary = ({ children }) => {
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    const errorHandler = (error) => {
      console.error(error);
      setHasError(true);
    };

    // Add a global error handler for uncaught errors
    window.addEventListener('error', errorHandler);

    // Clean up the event listener
    return () => {
      window.removeEventListener('error', errorHandler);
    };
  }, []);

  if (hasError) {
    // You can render a fallback UI when an error occurs
    return <div>Something went wrong.</div>;
  }

  return children;
};

// Example component that may throw an error
const MyComponent = () => {
  const throwError = () => {
    throw new Error('An error occurred');
  };

  return (
    <div>
      <button onClick={throwError}>Throw Error</button>
    </div>
  );
};

// App component
const App = () => {
  return (
    <div>
      <h1>Error Boundary Example</h1>
      <ErrorBoundary>
        <MyComponent />
      </ErrorBoundary>
    </div>
  );
};

export default App;

22. Lazy Loading and Suspense:

Implement code-splitting and lazy loading of components. Demonstrate how Suspense works with React.lazy and ErrorBoundary.

Code splitting and lazy loading of components in React allow you to load parts of your application only when they are needed, improving initial load times and reducing the size of the bundle. Additionally, React's Suspense component can be used with React.lazy and ErrorBoundary to handle loading and errors gracefully.

Here's how to implement code splitting, lazy loading, and demonstrate Suspense with React.lazy and ErrorBoundary in React functional components:

  1. Create a Dynamic Import

    First, create a dynamic import for the component you want to code split and lazy load. Let's assume we have a component called LazyComponent:

    // LazyComponent.js
    import React from 'react';
    
    const LazyComponent = () => {
      return <div>Lazy Component</div>;
    };
    
    export default LazyComponent;
  2. Lazy Load the Component

    Use React.lazy to dynamically load the component when needed. Wrap it with an ErrorBoundary to catch and handle any errors:

    // App.js
    import React, { Suspense } from 'react';
    
    // Define an ErrorBoundary component
    const ErrorBoundary = ({ children }) => {
      // Error handling logic here
    
      return children;
    };
    
    // Lazy load the component with React.lazy
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
    const App = () => {
      return (
        <div>
          <h1>Lazy Loading and Suspense Example</h1>
          <ErrorBoundary>
            <Suspense fallback={<div>Loading...</div>}>
              <LazyComponent />
            </Suspense>
          </ErrorBoundary>
        </div>
      );
    };
    
    export default App;

    In the above code, we use React.lazy(() => import('./LazyComponent')) to load LazyComponent lazily when it's needed. The Suspense component wraps LazyComponent and provides a fallback UI (in this case, "Loading...") while the component is being loaded.

To implement a lazy-loaded component in routing in a React application, you can use React's React.lazy along with React Router. Lazy loading components in routing can significantly improve the initial load time of your application by loading components only when they are needed.

  1. Error Handling with ErrorBoundary

    Define an ErrorBoundary component to catch and handle errors. You can implement your error handling logic within this component:

    const ErrorBoundary = ({ children }) => {
      const [hasError, setHasError] = React.useState(false);
    
      React.useEffect(() => {
        // Simulate an error for demonstration purposes
        if (Math.random() > 0.5) {
          setHasError(true);
        }
      }, []);
    
      if (hasError) {
        return <div>Something went wrong.</div>;
      }
    
      return children;
    };

implement a lazy-loaded component in routing:

// App.js or your routing file
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

// Create a dynamic import for the LazyComponent
const LazyComponent = lazy(() => import('./LazyComponent'));

const LoadingFallback = () => <div>Loading...</div>;

const App = () => {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route
          path="/lazy"
          render={() => (
            <Suspense fallback={LoadingFallback}>
              <LazyComponent />
            </Suspense>
          )}
        />
        {/* Other routes */}
      </Switch>
    </Router>
  );
};

export default App;

23. Contextual Component Styling:

Style components using CSS-in-JS libraries like styled-components or Emotion. Discuss the benefits of styled-components.

styled-components

Styled-components and Emotion are popular CSS-in-JS libraries that allow you to style React components using JavaScript. These libraries provide a way to write and manage CSS directly within your JavaScript code. Below, I'll demonstrate how to style components using styled-components in a React functional component and discuss the benefits of using styled-components.

Using styled-components in a React Functional Component:

First, make sure you have styled-components installed in your project:

npm install styled-components

Here's an example of how to use styled-components in a React functional component:

import React from 'react';
import styled from 'styled-components';

// Create a styled component
const StyledButton = styled.button`
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
  transition: background-color 0.3s;

  &:hover {
    background-color: #0056b3;
  }
`;

const App = () => {
  return (
    <div>
      <h1>Styled Components Example</h1>
      <StyledButton>Click me</StyledButton>
    </div>
  );
};

export default App;

In this example:

  1. We import the styled object from styled-components.

  2. We create a StyledButton component by using a tagged template literal with backticks (``). This allows you to write CSS directly within the JavaScript code. The styles defined in the template literal will be applied to the button element.

  3. Inside the App component, we use the StyledButton component, and it will render a styled button with the specified CSS styles.

Benefits of styled-components:

  1. Scoped Styles: Each styled-component generates unique class names, ensuring that styles are scoped to the component they are defined in. This prevents naming conflicts and global style pollution.

  2. Dynamic Styles: styled-components allows you to use JavaScript variables and props to conditionally apply styles, making it easy to create dynamic and responsive designs.

  3. Component-Based Styling: Styled-components encourages a component-based approach to styling, which aligns with React's component-based architecture. This makes it easier to reason about and maintain your styles.

  4. No Build Configuration: You can use styled-components without any additional build configuration. It works seamlessly with popular bundlers like Webpack and Create React App.

  5. Theming: styled-components supports theming, allowing you to define a set of theme variables and easily apply them to your styled components.

  6. Auto Vendor Prefixing: styled-components automatically applies vendor prefixes to CSS properties, ensuring cross-browser compatibility without the need for additional configuration.

  7. Interpolation: You can interpolate JavaScript values and functions directly into your styles, making it powerful and flexible for complex styling needs.

  8. Server-Side Rendering (SSR) Support: styled-components has built-in support for SSR, ensuring that styles are correctly rendered on the server and the client.

  9. Strong Community and Ecosystem: styled-components has a large and active community, and it's widely adopted in the React ecosystem. This means you can find plenty of documentation, third-party libraries, and resources to help you get started.

  10. Excellent Developer Experience: Many developers find the syntax of styled-components intuitive and enjoyable to work with. It provides a great developer experience for styling React components.

Overall, styled-components is a powerful and versatile CSS-in-JS library that offers many benefits for styling React applications. It promotes a more maintainable, scalable, and component-focused approach to styling, making it a popular choice in the React community.

emotion

Using Emotion in a React Functional Component:

First, make sure you have @emotion/react and @emotion/styled installed in your project:

npm install @emotion/react @emotion/styled

Here's an example of how to use Emotion in a React functional component:

import React from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';

// Define styles using the css function
const buttonStyles = css`
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
  transition: background-color 0.3s;

  &:hover {
    background-color: #0056b3;
  }
`;

// Create a styled component using styled function
const StyledButton = styled.button`
  ${buttonStyles}
`;

const App = () => {
  return (
    <div>
      <h1>Emotion Example</h1>
      <StyledButton>Click me</StyledButton>
    </div>
  );
};

export default App;

In this example:

  1. We import css and styled from @emotion/react and @emotion/styled, respectively.

  2. We define the buttonStyles using the css function, similar to how you'd use tagged template literals with styled-components. The styles are defined as a template literal.

  3. We create a StyledButton component using the styled function, which takes the buttonStyles as a template string. This component will render a styled button with the specified styles.

Comparison between Emotion and styled-components:

Both Emotion and styled-components are popular CSS-in-JS libraries with similar capabilities, but they have some differences:

  1. Syntax: Emotion and styled-components have similar syntax. They both allow you to write CSS within JavaScript using template literals or functions. The choice between them often comes down to personal preference.

  2. Performance: Both libraries offer excellent runtime performance and minimal bundle size. However, Emotion has an edge in some benchmarks due to its efficient CSS generation.

  3. Composition: Emotion provides additional features for style composition, allowing you to extend and override styles more easily. styled-components also supports composition but might require a different approach.

  4. Themability: Both libraries support theming, but Emotion's theming system is slightly more flexible, allowing for easier theming of nested components.

  5. Global Styles: styled-components has better support for global styles, while Emotion primarily focuses on component-level styles.

  6. Community and Ecosystem: styled-components has a larger community and a more extensive ecosystem of third-party tools and plugins. Emotion has a growing community and is widely adopted as well.

  7. Development Experience: The developer experience is subjective, but some developers prefer Emotion for its more minimalistic API, while others prefer styled-components for its additional features and tooling.

  8. Server-Side Rendering (SSR): Both libraries support SSR out of the box, but Emotion's SSR is considered slightly more efficient due to its faster CSS generation.

  9. Tooling: styled-components offers a wide range of tooling and integrations, including browser extensions and editor support. Emotion has tooling as well, but styled-components' tooling is more mature.

In summary, both Emotion and styled-components are excellent choices for styling React applications. The choice between them often depends on your specific project requirements and personal preference. Both libraries offer strong performance, component-level styling, theming, and a developer-friendly approach to styling in React.

Render Nested tree

To render an array of nested objects using recursion in React, you can create a recursive component that renders itself for each nested level. Here's a code example demonstrating how to do this:

import React from 'react';

function NestedList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
          {item.children && <NestedList items={item.children} />}
        </li>
      ))}
    </ul>
  );
}

function MyComponent() {
  // Sample array of nested objects
  const arrayOfNestedObjects = [
    {
      id: 1,
      name: 'Item 1',
      children: [
        {
          id: 101,
          name: 'Item 1.1',
          children: [
            { id: 10101, name: 'Item 1.1.1' },
            { id: 10102, name: 'Item 1.1.2' },
          ],
        },
        {
          id: 102,
          name: 'Item 1.2',
          children: [
            { id: 10201, name: 'Item 1.2.1' },
            { id: 10202, name: 'Item 1.2.2' },
          ],
        },
      ],
    },
    {
      id: 2,
      name: 'Item 2',
      children: [
        { id: 201, name: 'Item 2.1' },
        { id: 202, name: 'Item 2.2' },
      ],
    },
  ];

  return (
    <div>
      <h2>Nested List of Items</h2>
      <NestedList items={arrayOfNestedObjects} />
    </div>
  );
}

export default MyComponent;

In this example:

  1. We have a NestedList component that takes an items prop, which is an array of objects. Inside the NestedList component, we use map to iterate through the items.

  2. For each item, we render a list item (<li>) with the item's name. If the item has a children property (indicating that it's a nested object), we recursively render another NestedList component with the nested items.

  3. We set unique key props for each list item to help React efficiently update and re-render the list.

  4. In the MyComponent, we pass the arrayOfNestedObjects to the top-level NestedList component.

The rendered list will display nested items in a hierarchical structure based on the object's children property.

This approach allows you to render nested structures of objects using recursion in React. It's useful for rendering tree-like data structures or nested menus, where the depth of nesting can vary.

React UseFetch hook

Get post axios, fetch

You can use async/await syntax to make your code cleaner and easier to read when dealing with asynchronous operations like HTTP requests using fetch and Axios. Here's how you can rewrite the previous code snippets using async/await:

Using async/await with Fetch API:

// GET Request with Fetch and async/await
async function fetchPost() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log('GET Request with Fetch and async/await:', data);
  } catch (error) {
    console.error('Error:', error);
  }
}

// POST Request with Fetch and async/await
async function postNewData() {
  const postData = {
    title: 'foo',
    body: 'bar',
    userId: 1
  };

  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(postData)
    });
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log('POST Request with Fetch and async/await:', data);
  } catch (error) {
    console.error('Error:', error);
  }
}

// Call the functions
fetchPost();
postNewData();

Using async/await with Axios:

const axios = require('axios'); // Node.js environment

// GET Request with Axios and async/await
async function axiosGet() {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
    console.log('GET Request with Axios and async/await:', response.data);
  } catch (error) {
    console.error('Error:', error);
  }
}

// POST Request with Axios and async/await
async function axiosPost() {
  const postData = {
    title: 'foo',
    body: 'bar',
    userId: 1
  };

  try {
    const response = await axios.post('https://jsonplaceholder.typicode.com/posts', postData);
    console.log('POST Request with Axios and async/await:', response.data);
  } catch (error) {
    console.error('Error:', error);
  }
}

// Call the functions
axiosGet();
axiosPost();

In these examples, we use async functions (fetchPost, postNewData, axiosGet, axiosPost) to encapsulate the asynchronous operations, and we await the results of those operations. This makes the code more readable and easier to understand. Error handling is also done in a more structured way using try/catch blocks.

24. Testing:

Write unit tests for React components using testing libraries like Jest and React Testing Library. Test component interactions and asynchronous behavior.

25. Optimization:

Optimize component rendering with shouldComponentUpdate, memo, or PureComponent. Discuss techniques for optimizing React applications.

26. Integration with APIs:

Fetch data from an API and display it in a React component. Handle API errors and loading states.

27. Authentication and Authorization:

Implement user authentication and authorization in a React application. Secure routes and components based on user roles.

28. Server-Side Rendering (SSR):

Explain the benefits of SSR and how to implement it in a React application. Set up server-side rendering using Next.js or another framework.

29. Performance Optimization:

Identify performance bottlenecks in a React application and suggest improvements. Use tools like React DevTools and Lighthouse for profiling.

30. Context API or Redux Middleware:

Create custom middleware for handling actions in Redux. Use context API middleware for global state management.

31. Animation and Transitions:

Implement animations and transitions in React components using libraries like React Spring or CSS transitions.

32. React versions - React 18

⚠️ **GitHub.com Fallback** ⚠️