Skip to content

Instantly share code, notes, and snippets.

@nquangit
Last active May 16, 2024 06:03
Show Gist options
  • Save nquangit/b896c15f4b21ea391ff790260d06d9ac to your computer and use it in GitHub Desktop.
Save nquangit/b896c15f4b21ea391ff790260d06d9ac to your computer and use it in GitHub Desktop.

React Learning notes


1. React

1.1. Introduction

  • A frontend JavaScript framework, a JavaScript library for building UI (user interfaces) created by Facebook.
  • Used to build SPA (single-page applications) - a web app that loads only a single web document, and then updates the body content of that single document via JavaScript APIs such as Fetch when different content is to be shown.
  • Allows us to create reusable UI components.

1.2. Some key concepts

  • Components: The fundamental building blocks of a React application. A component is a reusable piece of code that encapsulates the HTML, CSS, and JavaScript code required to render a piece of the UI (they can be simple UI elements like buttons or complex structures like entire pages.)

    • Function Components: A Javascript function that accepts props and returns a React element - JSX (JavaScript XML).
    • Class Components: A JavaScript class that extends React.Component and has a render method that returns a React element - JSX (JavaScript XML).
  • JSX: A syntax extension for JavaScript that looks similar to XML/HTML. It allows us to write HTML-like elements in JavaScript and place them in the DOM without using functions like createElement() or appendChild(). (You are not required to use JSX, but JSX makes it easier to write React applications.)

    // Example of JSX
    const element = <h1>Hello, world!</h1>;
  • Props: Short for properties, they are used to pass data from a parent component to a child component. They are read-only and immutable.

    // Example of props
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    const element = <Welcome name="Sara" />;
  • State: An object that holds some information that may change over the lifetime of the component. It is mutable and private to the component that owns it. When the state of a component changes, the component automatically re-renders.

    // Example of state
    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = { date: new Date() };
      }
      render() {
        return <h1>It is {this.state.date.toLocaleTimeString()}.</h1>;
      }
    }
  • Lifecycle Methods: Methods that are called automatically by React at specific points in the lifecycle of a component. They are used to perform some actions when the component is created, updated, or destroyed.

    • componentDidMount(): Invoked immediately after a component is mounted (inserted into the tree).
    • componentDidUpdate(): Invoked immediately after updating occurs.
    • componentWillUnmount(): Invoked immediately before a component is unmounted and destroyed.
  • Hooks (from React 16.8): functions that let you use state and other React features without writing a class. They allow you to use state and other React features in functional components.

    • useState(): A hook that allows you to add state to a functional component.
    • useEffect(): A hook that allows you to perform side effects in functional components.
    • useContext(): A hook that allows you to use the context API in functional components.
    • useReducer(): A hook that allows you to manage state in functional components.
  • Virtual DOM: A lightweight copy of the actual DOM. React uses the virtual DOM to minimize the number of updates to the actual DOM. When the state of a component changes, React updates the virtual DOM first, then compares it with the actual DOM, and finally updates only the parts of the actual DOM that have changed.

  • ES6 (React uses ES6): ECMAScript 6, also known as ES6 or ECMAScript 2015, is the latest version of the ECMAScript standard. It introduces many new features to JavaScript, such as arrow functions, classes, let and const declarations, template literals, destructuring, and spread syntax.

1.3. How React works

React creates a VIRTUAL DOM in memory. React only changes what needs to be changed!

  • React creates a virtual DOM (in memory) that is a lightweight copy of the actual DOM and updates it first.
  • React then compares the virtual DOM with the actual DOM and updates only the parts of the actual DOM that have changed.

React render HTML

Use createRoot() function and its method render() to render a React element into the DOM.

const element = <h1>Hello, world!</h1>;
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(element);
  • The createRoot Function: Creates a new Root object that represents a React root. It accepts a container element as an argument. Purpose: define the HTML element where a React component should be rendered.
  • The render Method: Renders a React element into the DOM. It accepts a React element as an argument. Purpose: render a React element into the DOM.

1.4. Implementing React

1.4.1. React directly in HTML

Using CDN links to include React in an HTML file.

<!-- React library -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!-- React DOM library -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babel, allows us to write JSX syntax and ES6 in older browsers. -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!DOCTYPE html>
<html>
  <head>
    <script
      src="https://unpkg.com/react@18/umd/react.development.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="mydiv"></div>

    <script type="text/babel">
      function Hello() {
        return <h1>Hello World!</h1>;
      }

      const container = document.getElementById("mydiv");
      const root = ReactDOM.createRoot(container);
      root.render(<Hello />);
    </script>
  </body>
</html>

This way of using React can be OK for testing purposes, but for production you will need to set up a React environment.

1.4.2. Create React App

  1. Install Node.js.

  2. Install npx (Node Package Runner) globally.

    npm install -g npx
  3. Create a new React app using Create React App named my-app.

    npx create-react-app my-app
  4. Change to the my-app directory and start the development server.

     cd my-app
     npm start
  5. Open the browser and navigate to http://localhost:3000/.

1.5. ES6 Features

1.5.1. Arrow Functions

  • Arrow functions are a more concise way to write functions in JavaScript.

    // Traditional function
    function add(a, b) {
      return a + b;
    }
    
    // Arrow function
    const add = (a, b) => {
      a + b;
    };
    
    // If the function has only one statement, you can omit the curly braces and the return keyword.
    const add = (a, b) => a + b;
    
    // If the function has only one parameter, you can omit the parentheses.
    const square = (x) => x * x;
    const square = (x) => x * x;

1.5.2. Variable Declarations: var, let, and const

  • var: The variable declared with var is function-scoped. It means that the variable is available within the function in which it is declared.

  • let: The variable declared with let is block-scoped. It means that the variable is available within the block in which it is declared.

  • const: The variable declared with const is also block-scoped. It means that the variable is available within the block in which it is declared. The value of a const variable cannot be changed once it is assigned.

    // var
    function varExample() {
      if (true) {
        var x = 10;
      }
      console.log(x); // 10
    }
    
    // let
    function letExample() {
      if (true) {
        let x = 10;
      }
      console.log(x); // ReferenceError: x is not defined
    }
    
    // const
    const PI = 3.14;
    PI = 3.14159; // TypeError: Assignment to constant variable.

1.5.3. Classes

  • A class is a type of function, but instead of using the keyword function to initiate it, we use the keyword class, and the properties are assigned inside a constructor() method.

    class Car {
      constructor(brand) {
        this.carname = brand;
      }
    }
    
    mycar = new Car("Ford");
  • Methods can be added to a class using the class method syntax.

    class Car {
      constructor(brand) {
        this.carname = brand;
      }
      present() {
        return "I have a " + this.carname;
      }
    }
    
    mycar = new Car("Ford");
    mycar.present();
  • Class Inheritance: The extends keyword is used in class declarations or class expressions.

    class Car {
      constructor(brand) {
        this.carname = brand;
      }
      present() {
        return "I have a " + this.carname;
      }
    }
    
    class Model extends Car {
      constructor(brand, mod) {
        super(brand);
        this.model = mod;
      }
      show() {
        return this.present() + ", it is a " + this.model;
      }
    }
    
    mycar = new Model("Ford", "Mustang");
    mycar.show();

1.5.4. Array Methods

There are many JavaScript array methods. Some of the most commonly used array methods are:

  • map(): Creates a new array with the results of calling a provided function on every element in the array.

    const numbers = [1, 2, 3, 4, 5];
    const doubled = numbers.map((number) => number * 2);
    console.log(doubled); // [2, 4, 6, 8, 10]
  • filter(): Creates a new array with all elements that pass the test implemented by the provided function.

    const numbers = [1, 2, 3, 4, 5];
    const even = numbers.filter((number) => number % 2 === 0);
    console.log(even); // [2, 4]
  • reduce(): Executes a reducer function on each element of the array, resulting in a single output value.

    const numbers = [1, 2, 3, 4, 5];
    const sum = numbers.reduce((total, number) => total + number, 0);
    console.log(sum); // 15
  • forEach(): Calls a function for each element in the array.

    const numbers = [1, 2, 3, 4, 5];
    numbers.forEach((number) => console.log(number));
  • find(): Returns the first element in the array that satisfies the provided testing function.

    const numbers = [1, 2, 3, 4, 5];
    const even = numbers.find((number) => number % 2 === 0);
    console.log(even); // 2
  • some(): Tests whether at least one element in the array passes the test implemented by the provided function.

    const numbers = [1, 2, 3, 4, 5];
    const even = numbers.some((number) => number % 2 === 0);
    console.log(even); // true
  • every(): Tests whether all elements in the array pass the test implemented by the provided function.

    const numbers = [1, 2, 3, 4, 5];
    const even = numbers.every((number) => number % 2 === 0);
    console.log(even); // false

1.5.5. Destructuring

  • Destructuring is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

    // Array Destructuring
    const numbers = [1, 2, 3];
    const [a, b, c] = numbers;
    console.log(a); // 1
    console.log(b); // 2
    console.log(c); // 3
    
    // Object Destructuring
    const person = { name: "John", age: 30 };
    const { name, age } = person;
    console.log(name); // John
    console.log(age); // 30

1.5.6. Spread Operator (...)

  • The spread operator is used to expand an iterable object into its individual elements.

    const numbers = [1, 2, 3];
    const newNumbers = [...numbers, 4, 5];
    console.log(newNumbers); // [1, 2, 3, 4, 5]
  • The spread operator can also be used to copy the properties of an object into another object.

    const person = { name: "John", age: 30 };
    const newPerson = {
      ...person,
    };
    console.log(newPerson); // { name: "John", age: 30 }

1.5.7. Modules

  • Modules are reusable pieces of code that can be exported from one program and imported for use in another program.

  • Rely on the export and import statements.

    • Exporting a module: The export statement is used to export functions, objects, or primitive values from a module.

      • Named Exports: export keyword is used to export multiple named exports.

        // Named Exports
        export const PI = 3.14;
        export function add(a, b) {
          return a + b;
        }
      • Default Exports: export default keyword is used to export a single default export. (Only one default export per module)

      // Default Export
      const PI = 3.14;
      export default PI;
    • Import a module: The import statement is used to import functions, objects, or primitive values from a module. (Named exports must be destructured using curly braces. Default exports are imported without curly braces.)

      // Named Exports
      import { PI, add } from "./math";
      
      // Default Export
      import PI from "./math";

1.5.8. Ternary Operator

  • The ternary operator is the only JavaScript operator that takes three operands: a condition followed by a question mark (?), then an expression to execute if the condition is truthy, followed by a colon (:) and an expression to execute if the condition is falsy.

    const age = 20;
    const message = age >= 18 ? "You are an adult" : "You are a minor";
    console.log(message); // You are an adult

1.6. React JSX

You are not required to use JSX, but JSX makes it easier to write React applications.

// Example of JSX
const element = <h1>Hello, world!</h1>;

// Example of without JSX
const element = React.createElement("h1", null, "Hello, world!");

1.6.1. JSX Expressions

  • JSX expressions are JavaScript expressions that can be used in JSX.

    const name = "John";
    const element = <h1>Hello, {name}</h1>;
    const myElement = <h1>React is {5 + 5} times better with JSX</h1>;
  • JSX expressions can be used in attributes.

    const url = "https://www.example.com";
    const element = <a href={url}>Click here</a>;
  • JSX expressions can be used in loops.

    const numbers = [1, 2, 3, 4, 5];
    const list = numbers.map((number) => <li>{number}</li>);
    const element = <ul>{list}</ul>;

1.6.2. Insert large blocks of HTML

  • To insert large blocks of HTML, you can use parentheses.

    const element = (
      <div>
        <h1>Hello, world!</h1>
        <p>Welcome to React</p>
      </div>
    );

1.6.3. One top level element

  • In JSX, you must have only one top-level element.

    // This will throw an error
    const element = <h1>Hello, world!</h1> <p>Welcome to React</p>;
    
    // This is correct
    const element = (
      <div>
        <h1>Hello, world!</h1>
        <p>Welcome to React</p>
      </div>
    );
  • You can use React.Fragment to group multiple elements without adding an extra node to the DOM.

    const element = (
      <>
        <h1>Hello, world!</h1>
        <p>Welcome to React</p>
      </>
    );

1.6.4. Element must be closed

  • In JSX, you must close all elements.

    // This will throw an error
    const element = <input type="text">;
    
    // This is correct
    const element = <input type="text" />;

1.6.5. Attributes in JSX

  • In JSX, you can use attributes to add HTML attributes to elements.

    const element = <img src="image.jpg" alt="My Image" />;
  • class attribute is replaced with className in JSX.

    const element = <div className="container">Hello, world!</div>;

1.6.6. Style attribute in JSX

  • In JSX, you can use the style attribute to add inline styles to elements.

    const myStyle = {
      color: "red",
      fontSize: "20px",
    };
    const element = <h1 style={myStyle}>Hello, world!</h1>;

1.7. React Components

We have 2 types of components in React:

  • Function Components: A JavaScript function that accepts props and returns a React element.
  • Class Components: A JavaScript class that extends React.Component and has a render method that returns a React element.

1.7.1. Create first React component

  • Function Component:

    function Welcome(props) {
      return <h1>Hello, world</h1>;
    }
    
    const element = <Welcome />;
  • Class Component:

    class Welcome extends React.Component {
      render() {
        return <h1>Hello, world</h1>;
      }
    }
    
    const element = <Welcome />;

1.7.2. Rendering a Component

  • To render a React component into the DOM, you can use the ReactDOM.render() method.

    const element = <Welcome />;
    ReactDOM.createRoot(document.getElementById("root"));
    root.render(element); // Or root.render(<Welcome />);

1.7.3. Props

  • Props are used to pass data from a parent component to a child component.

    // props in Function Component
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="John" />;
    
    // props in Class Component
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    const element = <Welcome name="John" />;

1.7.4. Components in components

  • You can use components inside other components.

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    function App() {
      return (
        <div>
          <Welcome name="John" />
          <Welcome name="Sara" />
          <Welcome name="Mike" />
        </div>
      );
    }
    
    const element = <App />;

1.7.5. Component in a file

  • You can create a component in a separate file and import it into another file.

    // Welcome.js
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    export default Welcome;
    // App.js
    import Welcome from "./Welcome";
    
    function App() {
      return (
        <div>
          <Welcome name="John" />
          <Welcome name="Sara" />
          <Welcome name="Mike" />
        </div>
      );
    }
    
    export default App;
    // index.js
    import App from "./App";
    
    const element = <App />;
    ReactDOM.createRoot(document.getElementById("root"));
    root.render(element);

1.7.6. React Class

Note:
  • Before React 16.8, class components were the only way to create stateful components in React.
  • With the introduction of Hooks in React 16.8, you can now use state and other React features in functional components.
  • The differences are so minor that you will probably never need to use a Class component in React.
  • Feel free to skip this section, and use Function Components instead.

1.7.6.1. Class Component

  • A class component is a JavaScript class that extends React.Component. The component's name must start with an upper case letter.

    class Welcome extends React.Component {
      render() {
        return <h1>Hello, world</h1>;
      }
    }
    
    const element = <Welcome />;

1.7.6.2. Constructor

  • The constructor method is a special method for creating and initializing an object created with a class.

    class Welcome extends React.Component {
      constructor(props) {
        super(props);
        this.state = { name: "John" };
      }
      render() {
        return <h1>Hello, {this.state.name}</h1>;
      }
    }
    
    const element = <Welcome />;

1.7.6.3. Props

  • You can access props in a class component using this.props.

    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    const element = <Welcome name="John" />;

1.7.6.4. State

  • State is an object that holds some information that may change over the lifetime of the component.

    class Welcome extends React.Component {
      constructor(props) {
        super(props);
        this.state = { name: "John" };
      }
      render() {
        return <h1>Hello, {this.state.name}</h1>;
      }
    }
    
    const element = <Welcome />;

1.7.7. React Props

  • Props are used to pass data from a parent component to a child component.

  • React Props are read-only! You will get an error if you try to change their value.

    // Function Component
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="John" />;
    
    // Class Component
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    const element = <Welcome name="John" />;
  • We can pass variables, functions, or objects as props.

    // Passing variables
    const name = "John";
    const element = <Welcome name={name} />;
    
    // Passing functions
    function handleClick() {
      alert("Button clicked");
    }
    const element = <button onClick={handleClick}>Click me</button>;
    
    // Passing objects
    const person = { name: "John", age: 30 };
    const element = <Welcome person={person} />;

1.7.8. React Events

  • Events are actions that occur when a user interacts with a web page.

    function handleClick() {
      alert("Button clicked");
    }
    
    const element = <button onClick={handleClick}>Click me</button>;
  • Event handlers are functions that are executed when an event occurs.

1.7.9. React Forms

  • Forms are used to collect user input in a web page.

    function MyForm() {
      return (
        <form>
          <label>
            Enter your name:
            <input type="text" />
          </label>
        </form>
      );
    }
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);
  • Handling Forms:

    HTML React
    Form data is usually handled by the DOM - Form data is usually handled by the components
    - The data is stored in the component state
    • Control changes by adding event handlers in the onChange attribute.
    • Use the useState Hook to keep track of each inputs value and provide a "single source of truth" for the entire application.
    function MyForm() {
      const [name, setName] = React.useState("");
      const handleChange = (event) => {
        setName(event.target.value);
      };
      return (
        <form>
          <label>
            Enter your name:
            <input type="text" value={name} onChange={handleChange} />
          </label>
          <p>Your name is: {name}</p>
        </form>
      );
    }
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);
  • Submitting Forms:

    • Use the onSubmit attribute to specify a function that will be called when the form is submitted.
    • Use the preventDefault() method to prevent the default action of the form.
    function MyForm() {
      const [name, setName] = React.useState("");
      const handleSubmit = (event) => {
        event.preventDefault();
        alert("Form submitted");
      };
      const handleChange = (event) => {
        setName(event.target.value);
      };
      return (
        <form onSubmit={handleSubmit}>
          <label>
            Enter your name:
            <input type="text" value={name} onChange={handleChange} />
          </label>
          <button type="submit">Submit</button>
        </form>
      );
    }
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);
  • Multiple input fields:

    • Use the name attribute to specify the name of the input field.
    • Use the value attribute to specify the value of the input field.
    • Use the onChange attribute to specify a function that will be called when the input field changes.
    import { useState } from "react";
    import ReactDOM from "react-dom/client";
    
    function MyForm() {
      const [inputs, setInputs] = useState({});
    
      const handleChange = (event) => {
        const name = event.target.name;
        const value = event.target.value;
        setInputs((values) => ({ ...values, [name]: value }));
      };
    
      const handleSubmit = (event) => {
        event.preventDefault();
        alert(inputs);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <label>
            Enter your name:
            <input
              type="text"
              name="username"
              value={inputs.username || ""}
              onChange={handleChange}
            />
          </label>
          <label>
            Enter your age:
            <input
              type="number"
              name="age"
              value={inputs.age || ""}
              onChange={handleChange}
            />
          </label>
          <input type="submit" />
        </form>
      );
    }
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);
  • Textarea:

    • Use the textarea element to create a multi-line text input field.
    • Use the value attribute to specify the value of the textarea.
    • Use the onChange attribute to specify a function that will be called when the textarea changes.
    function MyForm() {
      const [message, setMessage] = React.useState("");
      const handleChange = (event) => {
        setMessage(event.target.value);
      };
      return (
        <form>
          <label>
            Enter your message:
            <textarea value={message} onChange={handleChange} />
          </label>
          <p>Your message is: {message}</p>
        </form>
      );
    }
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);
  • Select:

    • Use the select element to create a drop-down list.
    • Use the value attribute to specify the value of the select element.
    • Use the onChange attribute to specify a function that will be called when the select element changes.
    function MyForm() {
      const [fruit, setFruit] = React.useState("apple");
      const handleChange = (event) => {
        setFruit(event.target.value);
      };
      return (
        <form>
          <label>
            Select your favorite fruit:
            <select value={fruit} onChange={handleChange}>
              <option value="apple">Apple</option>
              <option value="banana">Banana</option>
              <option value="orange">Orange</option>
            </select>
          </label>
          <p>Your favorite fruit is: {fruit}</p>
        </form>
      );
    }
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<MyForm />);

1.8. React Router

  • Create React App doesn't include page routing.

  • React Router is a collection of navigational components that compose declaratively with your application.

    npm i -D react-router-dom

1.8.1. Folder Structure

  • src: Contains the source code of the application.
  • src/components: Contains the components of the application.
  • src/pages: Contains the pages of the application.
  • src/App.js: The main component of the application.

src\pages\:

  • Home.js: The Home page of the application.
  • Layout.js: The layout component of the application.
  • Blog.js: The Blog page of the application.
  • Contact.js: The Contact page of the application.
  • NoPage.js: The 404 page of the application.

1.8.2. Basic Usage

// index.js
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./pages/Layout";
import Home from "./pages/Home";
import Blogs from "./pages/Blogs";
import Contact from "./pages/Contact";
import NoPage from "./pages/NoPage";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="blogs" element={<Blogs />} />
          <Route path="contact" element={<Contact />} />
          <Route path="*" element={<NoPage />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

Explain:

  • BrowserRouter: A router that uses the HTML5 history API to keep your UI in sync with the URL.
  • Routes: A collection of routes that render their children when their path matches the current URL. An application can have multiple <Routes>.
  • Route: A route that renders its children when its path matches the current URL. <Route>s can be nested.
    • The index attribute is used to specify the default route (/).
    • Setting path to * will match all paths that are not matched by other routes.

1.8.3 Pages / Components

// Layout.js
import { Outlet, Link } from "react-router-dom";

const Layout = () => {
  return (
    <>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/blogs">Blogs</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
        </ul>
      </nav>

      <Outlet />
    </>
  );
};

export default Layout;
// Home.js
const Home = () => {
  return <h1>Home Page</h1>;
};

export default Home;
// Blogs.js
const Blogs = () => {
  return <h1>Blogs Page</h1>;
};

export default Blogs;
// Contact.js
const Contact = () => {
  return <h1>Contact Page</h1>;
};

export default Contact;
// NoPage.js
const NoPage = () => {
  return <h1>404 Page</h1>;
};

export default NoPage;

Explain:

  • The Layout component has <Outlet> and <Link> elements.
  • The <Outlet> renders the current route selected.
  • <Link> is used to set the URL and keep track of browsing history.
  • Anytime we link to an internal path, we will use <Link> instead of <a href="">.
  • The "layout route" is a shared component that inserts common content on all pages, such as a navigation menu.

1.9. React Hooks

  • Allow function components to use state and other React features without writing a class.

1.9.1. What is a Hook?

  • Hooks are functions that let you use state and other React features in functional components.
  • Hooks are a new addition in React 16.8.
  • Hooks don't work inside classes.

Hook rules

  • Only call Hooks at the top level: Don't call Hooks inside loops, conditions, or nested functions.
  • Only call Hooks from React functions: Call Hooks from React function components and not just any regular JavaScript function or class.
  • Hooks cannot be conditional: Don't call Hooks conditionally.

1.9.2. useState()

To use state in a functional component, you can use the useState() Hook.

import { useState } from "react";
  • useState() is a Hook that allows you to add state to a functional component.

    import { useState } from "react";
    
    function Counter() {
      const [count, setCount] = useState(0);
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
      );
    }

Initializing State

useState() accepts an argument that represents the initial state value.

import { useState } from "react";

function FavoriteColor() {
  const [color, setColor] = useState("");
}

Reading State

import { useState } from "react";
import ReactDOM from "react-dom/client";

function FavoriteColor() {
  const [color, setColor] = useState("red");

  return <h1>My favorite color is {color}!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<FavoriteColor />);

Updating State

To update our state, we use our state updater function. This function will update the state and re-render the component. We should never directly update state. Ex: color = "red" is not allowed.

import { useState } from "react";
import ReactDOM from "react-dom/client";

function FavoriteColor() {
  const [color, setColor] = useState("red");

  return (
    <>
      <h1>My favorite color is {color}!</h1>
      <button type="button" onClick={() => setColor("blue")}>
        Blue
      </button>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<FavoriteColor />);

What Can State Hold

useState() can hold any type of data, such as strings, numbers, arrays, and objects.

const [name, setName] = useState("John");
const [age, setAge] = useState(30);
const [colors, setColors] = useState(["red", "green", "blue"]);
const [person, setPerson] = useState({ name: "John", age: 30 });

Updating State Based on Previous State

When updating state based on the previous state, you can pass a function to setState().

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

const increment = () => {
  setCount((prevCount) => prevCount + 1);
};

Updating State Based on Previous State (Object)

When updating state based on the previous state (object), you can pass a function to setState().

const [person, setPerson] = useState({ name: "John", age: 30 });

const updatePerson = () => {
  setPerson((prevPerson) => {
    return { ...prevPerson, age: prevPerson.age + 1 };
  });
};

1.9.3. useEffect()

  • The useEffect() Hook is used to perform side effects in a functional component.

  • Some examples of side effects are: fetching data, directly updating the DOM, and timers.

  • useEffect accepts two arguments. The second argument is optional.

    useEffect(() => {
      // Side effect
    }, [dependencies]);
    
    // Or
    useEffect(<function>, [dependencies])

Example:

// Use setTimeout() to count 1 second after initial render:
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

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

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  });

  return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Timer />);
  • useEffect runs on every render by default.

  • To run useEffect only once, pass an empty array as the second argument.

    useEffect(() => {
      // Side effect
    }, []);
  • To run useEffect only when a specific value changes, pass that value in the array.

    import { useState, useEffect } from "react";
    import ReactDOM from "react-dom/client";
    
    function Counter() {
      const [count, setCount] = useState(0);
      const [calculation, setCalculation] = useState(0);
    
      useEffect(() => {
        setCalculation(() => count * 2);
      }, [count]); // <- add the count variable here and if the count variable updates, the effect will run again:
    
      return (
        <>
          <p>Count: {count}</p>
          <button onClick={() => setCount((c) => c + 1)}>+</button>
          <p>Calculation: {calculation}</p>
        </>
      );
    }
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<Counter />);

Cleanup

  • useEffect() can return a function that will be called when the component is unmounted or before the effect runs again.

    useEffect(() => {
      // Side effect
      return () => {
        // Cleanup
      };
    }, []);
    useEffect(() => {
      // Note: To clear the timer, we had to name it.
      const interval = setInterval(() => {
        // Side effect
      }, 1000);
    
      return () => {
        clearInterval(interval);
      };
    }, []);

1.9.4. useContext()

  • The useContext() is a way to manage state globally.
  • The useContext() Hook is used to access the value of a context.

Create a Context

  • Create a context using the createContext() function.

    import { useState, createContext } from "react";
    import ReactDOM from "react-dom/client";
    
    const UserContext = createContext();

Provide a Context

  • Use the Provider component to provide the context value to its children.

    const App = () => {
      const [user, setUser] = useState("John");
    
      return (
        <UserContext.Provider value={user}>
          <h1>{`Hello ${user}!`}</h1>
          <Component2 user={user} />
        </UserContext.Provider>
      );
    };

    Now, all components in this tree will have access to the user Context.

Use a Context

  • Use the useContext() Hook to access the value of a context.

    import { useState, createContext, useContext } from "react";
    
    const Component2 = () => {
      const user = useContext(UserContext);
    
      return <h2>{`Hello ${user}!`}</h2>;
    };

Full Example

import { useState, createContext, useContext } from "react";
import ReactDOM from "react-dom/client";

const UserContext = createContext();

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 />
    </UserContext.Provider>
  );
}

function Component2() {
  return (
    <>
      <h1>Component 2</h1>
      <Component3 />
    </>
  );
}

function Component3() {
  return (
    <>
      <h1>Component 3</h1>
      <Component4 />
    </>
  );
}

function Component4() {
  return (
    <>
      <h1>Component 4</h1>
      <Component5 />
    </>
  );
}

function Component5() {
  const user = useContext(UserContext);

  return (
    <>
      <h1>Component 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Component1 />);

1.9.5. useRef()

  • The useRef() Hook is used to create a mutable reference that persists for the lifetime of the component.
  • It can be used to access a DOM element directly.

Does Not Cause Re-renders

  • useRef() does not cause re-renders when the value changes.

    // Use useRef to track application renders.
    import { useState, useEffect, useRef } from "react";
    import ReactDOM from "react-dom/client";
    
    function App() {
      const [inputValue, setInputValue] = useState("");
      const count = useRef(0);
    
      useEffect(() => {
        count.current = count.current + 1;
      });
    
      return (
        <>
          <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
          />
          <h1>Render Count: {count.current}</h1>
        </>
      );
    }
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App />);
  • useRef() only returns one item. It returns an Object called current.

  • It's like doing this: const count = {current: 0}. We can access the count by using count.current.

Accessing DOM Elements

  • useRef() can be used to access DOM elements directly.

    import { useRef } from "react";
    
    function App() {
      const inputRef = useRef();
    
      const handleClick = () => {
        inputRef.current.focus();
      };
    
      return (
        <>
          <input type="text" ref={inputRef} />
          <button onClick={handleClick}>Focus</button>
        </>
      );
    }

Tracking state changes

// Use useRef to keep track of previous state values:
import { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom/client";

function App() {
  const [inputValue, setInputValue] = useState("");
  const previousInputValue = useRef("");

  useEffect(() => {
    previousInputValue.current = inputValue;
  }, [inputValue]);

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h2>Current Value: {inputValue}</h2>
      <h2>Previous Value: {previousInputValue.current}</h2>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

1.9.6. useReducer()

  • The useReducer() Hook is used to manage complex state logic in a component.
  • It is similar to useState(), but it accepts a reducer function with the current state and an action.

Syntax

// useReducer(<reducer>, <initialState>)
const [state, dispatch] = useReducer(reducer, initialState);
  • The reducer function contains your custom state logic and the initialState can be a simple value but generally will contain an object.
  • The useReducer Hook returns the current state and a dispatch method.

Usage

import { useReducer } from "react";
import ReactDOM from "react-dom/client";

const initialTodos = [
  {
    id: 1,
    title: "Todo 1",
    complete: false,
  },
  {
    id: 2,
    title: "Todo 2",
    complete: false,
  },
];

const reducer = (state, action) => {
  switch (action.type) {
    case "COMPLETE":
      return state.map((todo) => {
        if (todo.id === action.id) {
          return { ...todo, complete: !todo.complete };
        } else {
          return todo;
        }
      });
    default:
      return state;
  }
};

function Todos() {
  const [todos, dispatch] = useReducer(reducer, initialTodos);

  const handleComplete = (todo) => {
    dispatch({ type: "COMPLETE", id: todo.id });
  };

  return (
    <>
      {todos.map((todo) => (
        <div key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.complete}
              onChange={() => handleComplete(todo)}
            />
            {todo.title}
          </label>
        </div>
      ))}
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Todos />);
  • This is just the logic to keep track of the todo complete status.
  • All of the logic to add, delete, and complete a todo could be contained within a single useReducer Hook by adding more actions.

1.9.7. useCallback()

  • The useCallback() Hook is used to memoize functions.
  • It returns a memoized version of the callback function that only changes if one of the dependencies has changed.

Note: Not done yet

1.9.8. useMemo()

  • The useMemo() Hook is used to memoize values.
  • It returns a memoized value that only changes when one of the dependencies has changed.
  • It is similar to useCallback(), but it is used for memoizing values instead of functions.

Note: Not done yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment