Skip to content

React Basic 1 ContactList

https://stackblitz.com/edit/react-j2anynkk?file=README.md

  • Make a GET request to "https://jsonplaceholder.typicode.com/users"
  • Display all users as contacts and the associated properties (name, email, website) in a table
  • A user should be able to add a new contact by filling out the form and clicking on the "Add" button
  • If any of the fields are empty, users should not be able to add a new contact, and the application should alert with the message "Fields cannot be empty!"

https://jsonplaceholder.typicode.com/users:

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  {
    "id": 3,
    "name": "Clementine Bauch",
    "username": "Samantha",
    "email": "Nathan@yesenia.net",
    "address": {
      "street": "Douglas Extension",
      "suite": "Suite 847",
      "city": "McKenziehaven",
      "zipcode": "59590-4157",
      "geo": {
        "lat": "-68.6102",
        "lng": "-47.0653"
      }
    },
    "phone": "1-463-123-4447",
    "website": "ramiro.info",
    "company": {
      "name": "Romaguera-Jacobson",
      "catchPhrase": "Face to face bifurcated interface",
      "bs": "e-enable strategic applications"
    }
  },
  {
    "id": 4,
    "name": "Patricia Lebsack",
    "username": "Karianne",
    "email": "Julianne.OConner@kory.org",
    "address": {
      "street": "Hoeger Mall",
      "suite": "Apt. 692",
      "city": "South Elvis",
      "zipcode": "53919-4257",
      "geo": {
        "lat": "29.4572",
        "lng": "-164.2990"
      }
    },
    "phone": "493-170-9623 x156",
    "website": "kale.biz",
    "company": {
      "name": "Robel-Corkery",
      "catchPhrase": "Multi-tiered zero tolerance productivity",
      "bs": "transition cutting-edge web services"
    }
  },
  {
    "id": 5,
    "name": "Chelsey Dietrich",
    "username": "Kamren",
    "email": "Lucio_Hettinger@annie.ca",
    "address": {
      "street": "Skiles Walks",
      "suite": "Suite 351",
      "city": "Roscoeview",
      "zipcode": "33263",
      "geo": {
        "lat": "-31.8129",
        "lng": "62.5342"
      }
    },
    "phone": "(254)954-1289",
    "website": "demarco.info",
    "company": {
      "name": "Keebler LLC",
      "catchPhrase": "User-centric fault-tolerant solution",
      "bs": "revolutionize end-to-end systems"
    }
  },
  {
    "id": 6,
    "name": "Mrs. Dennis Schulist",
    "username": "Leopoldo_Corkery",
    "email": "Karley_Dach@jasper.info",
    "address": {
      "street": "Norberto Crossing",
      "suite": "Apt. 950",
      "city": "South Christy",
      "zipcode": "23505-1337",
      "geo": {
        "lat": "-71.4197",
        "lng": "71.7478"
      }
    },
    "phone": "1-477-935-8478 x6430",
    "website": "ola.org",
    "company": {
      "name": "Considine-Lockman",
      "catchPhrase": "Synchronised bottom-line interface",
      "bs": "e-enable innovative applications"
    }
  },
  {
    "id": 7,
    "name": "Kurtis Weissnat",
    "username": "Elwyn.Skiles",
    "email": "Telly.Hoeger@billy.biz",
    "address": {
      "street": "Rex Trail",
      "suite": "Suite 280",
      "city": "Howemouth",
      "zipcode": "58804-1099",
      "geo": {
        "lat": "24.8918",
        "lng": "21.8984"
      }
    },
    "phone": "210.067.6132",
    "website": "elvis.io",
    "company": {
      "name": "Johns Group",
      "catchPhrase": "Configurable multimedia task-force",
      "bs": "generate enterprise e-tailers"
    }
  },
  {
    "id": 8,
    "name": "Nicholas Runolfsdottir V",
    "username": "Maxime_Nienow",
    "email": "Sherwood@rosamond.me",
    "address": {
      "street": "Ellsworth Summit",
      "suite": "Suite 729",
      "city": "Aliyaview",
      "zipcode": "45169",
      "geo": {
        "lat": "-14.3990",
        "lng": "-120.7677"
      }
    },
    "phone": "586.493.6943 x140",
    "website": "jacynthe.com",
    "company": {
      "name": "Abernathy Group",
      "catchPhrase": "Implemented secondary concept",
      "bs": "e-enable extensible e-tailers"
    }
  },
  {
    "id": 9,
    "name": "Glenna Reichert",
    "username": "Delphine",
    "email": "Chaim_McDermott@dana.io",
    "address": {
      "street": "Dayna Park",
      "suite": "Suite 449",
      "city": "Bartholomebury",
      "zipcode": "76495-3109",
      "geo": {
        "lat": "24.6463",
        "lng": "-168.8889"
      }
    },
    "phone": "(775)976-6794 x41206",
    "website": "conrad.com",
    "company": {
      "name": "Yost and Sons",
      "catchPhrase": "Switchable contextually-based project",
      "bs": "aggregate real-time technologies"
    }
  },
  {
    "id": 10,
    "name": "Clementina DuBuque",
    "username": "Moriah.Stanton",
    "email": "Rey.Padberg@karina.biz",
    "address": {
      "street": "Kattie Turnpike",
      "suite": "Suite 198",
      "city": "Lebsackbury",
      "zipcode": "31428-2261",
      "geo": {
        "lat": "-38.2386",
        "lng": "57.2232"
      }
    },
    "phone": "024-648-3804",
    "website": "ambrose.net",
    "company": {
      "name": "Hoeger LLC",
      "catchPhrase": "Centralized empowering task-force",
      "bs": "target end-to-end models"
    }
  }
]

/src/components/Form.js

import React from 'react';

export default function Form() {
  return (
    <form className="form">
      <h1>Contact Form</h1>
      <input
        className="input"
        placeholder="Name"/>
      <input
        className="input"
        placeholder="Email"/>
      <input
        className="input"
        placeholder="Website"/>
      <button className="btn" type="submit">Add</button>
    </form> 
  )
}

1 Make a GET

  • Make a GET request to "https://jsonplaceholder.typicode.com/users"

App.js

import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息

// 定义 App 组件,并将其作为默认导出
export default function App() {
  const [users, setUsers] = useState([]);
  // users:存储用户列表的状态变量(初始为空数组 []
  // setUsers:用于更新 users 的函数

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json()) // 解析服务器返回的 JSON 数据。
      .then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
      .catch((error) => console.error('Errot fetching user:', error));
    // 如果 fetch 失败(如网络错误),则打印错误信息
    // return () => {
    //   console.log('unMount');
    // };
  }, []);

  // useEffect(() => { ... }, []);
  // useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
  // 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)

  // fetch('https://jsonplaceholder.typicode.com/users')
  // 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表

  return (
    <div className="container">
      <h1>Contacts</h1>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>name</td>
            <td>email</td>
            <td>website</td>
          </tr>
        </tbody>
      </table>
      <Form />
    </div>
  );
}

2 Display all users

  • Display all users as contacts and the associated properties (name, email, website) in a table
import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息

// 定义 App 组件,并将其作为默认导出
export default function App() {
  const [users, setUsers] = useState([]);
  // users:存储用户列表的状态变量(初始为空数组 []
  // setUsers:用于更新 users 的函数

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json()) // 解析服务器返回的 JSON 数据。
      .then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
      .catch((error) => console.error('Errot fetching user:', error));
    // 如果 fetch 失败(如网络错误),则打印错误信息
    // return () => {
    //   console.log('unMount');
    // };
  }, []);

  // useEffect(() => { ... }, []);
  // useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
  // 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)

  // fetch('https://jsonplaceholder.typicode.com/users')
  // 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表

  // return 语句返回 App 组件的 UI 结构
  return (
    // 容器 div,用于包裹整个组件
    <div className="container">
      <h1>Contacts</h1>
      {/* 
        标题 “Contacts”
      */}
      <table>
        {/* <table>: HTML 表格,用于显示联系人信息 */}
        <thead>
          {/* <thead>:表头部分,定义列名(Name、Email、Website) */}
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
          </tr>
        </thead>
        <tbody>
          {/* <tbody>:表格主体 */}
          {/* users.map((user) => (...)) 
          遍历 users 数组,为每个 user 生成一个 <tr> 行 */}
          {users.map((user) => (
            <tr>
              {/* <tr key={user.id}> */}
              {/* key={user.id}:使用 user.id 作为 React 的唯一标识符,防止重复*/}
              <td>{user.name}</td>
              {/* 显示用户的 name(名字)*/}
              <td>{user.email}</td>
              {/* 显示用户的 email(邮箱)*/}
              <td>{user.website}</td>
              {/* 显示用户的 website(网站)*/}
            </tr>
          ))}
        </tbody>
      </table>
      <Form />
    </div>
  );
}

Screenshot 2025-02-01 at 17.59.54

3 Add a new contact

  • A user should be able to add a new contact by filling out the form and clicking on the "Add" button

App.js:

import React, { useState, useEffect } from 'react';
// useState 是 React 内置的 Hook,需要先从 react 中导入,否则 JavaScript 不知道 useState 是什么
import './style.css';
// 引入样式文件,使页面具有良好的 UI 设计
import Form from './components/Form.js';
// 引入 Form 组件,该组件用于输入新的联系人信息

// 定义 App 组件,并将其作为默认导出
export default function App() {
  const [users, setUsers] = useState([]);
  // users:存储用户列表的状态变量(初始为空数组 []
  // setUsers:用于更新 users 的函数

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json()) // 解析服务器返回的 JSON 数据。
      .then((data) => setUsers(data)) // 将获取到的 data 设置到 users 状态变量中,以便在 UI 上展示
      .catch((error) => console.error('Errot fetching user:', error));
    // 如果 fetch 失败(如网络错误),则打印错误信息
    // return () => {
    //   console.log('unMount');
    // };
  }, []);

  // useEffect(() => { ... }, []);
  // useEffect 作用是在组件加载时执行 fetch 请求,获取用户数据
  // 由于 [] 为空数组,所以这个 useEffect 只会执行一次(相当于 componentDidMount)

  // fetch('https://jsonplaceholder.typicode.com/users')
  // 向 jsonplaceholder.typicode.com 这个假 API 发送 GET 请求,以获取用户列表

  // return 语句返回 App 组件的 UI 结构

  // 定义 addContact 函数,用于添加新联系人
  // const addContact → 定义一个常量变量 addContact,它是一个函数
  // (newContact) => {} → 箭头函数,它的参数是 newContact,表示新联系人数据
  const addContact = (newContact) => {
    setUsers([...users, newContact]);
    // 使用 setUsers 更新用户列表
    // ...users:展开现有的 users 数组
    // newContact:添加新的联系人对象到 users 末尾
  };
  // const 是 JavaScript 中用于声明 常量(constant)的关键字。它表示 声明的变量不能重新赋值,但如果变量是一个对象或数组,我们仍然可以修改它的内容

  // () => {} 是 箭头函数(Arrow Function) 的语法。它是 JavaScript ES6+ 提供的一种更简洁的函数写法,相当于:
  // function functionName() {
  //   // 代码
  // }
  // 用箭头函数表示:
  // const functionName = () => {
  //   // 代码
  // };

  return (
    // 容器 div,用于包裹整个组件
    <div className="container">
      <h1>Contacts</h1>
      {/* 
        标题 “Contacts”
      */}
      <table>
        {/* <table>: HTML 表格,用于显示联系人信息 */}
        <thead>
          {/* <thead>:表头部分,定义列名(Name、Email、Website) */}
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
          </tr>
        </thead>
        <tbody>
          {/* <tbody>:表格主体 */}
          {/* users.map((user) => (...)) 
          遍历 users 数组,为每个 user 生成一个 <tr> 行 */}
          {users.map((user) => (
            <tr>
              {/* <tr key={user.id}> */}
              {/* key={user.id}:使用 user.id 作为 React 的唯一标识符,防止重复*/}
              <td>{user.name}</td>
              {/* 显示用户的 name(名字)*/}
              <td>{user.email}</td>
              {/* 显示用户的 email(邮箱)*/}
              <td>{user.website}</td>
              {/* 显示用户的 website(网站)*/}
            </tr>
          ))}
        </tbody>
      </table>
      <Form onAddContact={addContact} />
      {/* <Form onAddContact={addContact} />
Form 组件,用于新增联系人,并将 addContact 作为 prop 传递 */}
    </div>
  );
}

/src/components/Form.js:

import React, { useState } from 'react';

export default function Form({ onAddContact }) {
  // 定义 Form 组件,并接收 onAddContact 作为 prop,用于添加联系人
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [website, setWebsite] = useState('');
  // name、email、website 分别管理输入框的状态,初始值为空字符串 ''

  // Handle form submission
  // handleSubmit(e):监听表单提交事件 onSubmit
  const handleSubmit = (e) => {
    e.preventDefault(); // 阻止默认提交行为,避免页面刷新
    /*

    在 HTML 表单中,如果你点击提交按钮(<button type="submit">)或者按下 Enter 键,默认行为是将表单数据提交给服务器,并且 浏览器会刷新页面。

    在某些情况下,我们希望 拦截表单提交事件,然后用 JavaScript 处理数据(比如使用 AJAX 发送请求,而不是让页面刷新)。这时,就可以用 e.preventDefault(); 来 阻止默认提交行为

    此处要是不阻止 就会刷新, 新添加的就会不见了.

    */

    const newContact = { name, email, website, id: Date.now() };
    // 创建 newContact 对象,包含 name、email、website,并用 Date.now() 生成唯一 id
    onAddContact(newContact);
    // 调用 onAddContact,将新联系人添加到 App.js 的 users 状态中。

    // Clear the form
    setName('');
    setEmail('');
    setWebsite('');
  };

  return (
    <form className="form" onSubmit={handleSubmit}>
      {/* <form> 绑定 onSubmit={handleSubmit},点击提交时执行 handleSubmit 逻辑 */}
      <h1>Contact Form</h1>
      <input
        className="input"
        placeholder="Name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      {/* value={name} 绑定 name 变量,onChange={(e) => setName(e.target.value)} 更新状态 */}
      <input
        className="input"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        className="input"
        placeholder="Website"
        value={website}
        onChange={(e) => setWebsite(e.target.value)}
      />
      <button className="btn" type="submit">
        Add
      </button>
    </form>
  );
}

4 Fields cannot be empty

  • If any of the fields are empty, users should not be able to add a new contact, and the application should alert with the message "Fields cannot be empty!"

/src/components/Form.js:

import React, { useState } from 'react';

export default function Form({ onAddContact }) {
  // 定义 Form 组件,并接收 onAddContact 作为 prop,用于添加联系人
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [website, setWebsite] = useState('');
  // name、email、website 分别管理输入框的状态,初始值为空字符串 ''

  // Handle form submission
  // handleSubmit(e):监听表单提交事件 onSubmit
  const handleSubmit = (e) => {
    e.preventDefault(); // 阻止默认提交行为,避免页面刷新
    /*

    在 HTML 表单中,如果你点击提交按钮(<button type="submit">)或者按下 Enter 键,默认行为是将表单数据提交给服务器,并且 浏览器会刷新页面。

    在某些情况下,我们希望 拦截表单提交事件,然后用 JavaScript 处理数据(比如使用 AJAX 发送请求,而不是让页面刷新)。这时,就可以用 e.preventDefault(); 来 阻止默认提交行为

    此处要是不阻止 就会刷新, 新添加的就会不见了.

    */

    // Validate that fields are not empty
    if (!name || !email || !website) {
      alert('Fields cannot be empty!');
      return;
    }

    const newContact = { name, email, website, id: Date.now() };
    // 创建 newContact 对象,包含 name、email、website,并用 Date.now() 生成唯一 id
    onAddContact(newContact);
    // 调用 onAddContact,将新联系人添加到 App.js 的 users 状态中。

    // Clear the form
    setName('');
    setEmail('');
    setWebsite('');
  };

  return (
    <form className="form" onSubmit={handleSubmit}>
      {/* <form> 绑定 onSubmit={handleSubmit},点击提交时执行 handleSubmit 逻辑 */}
      <h1>Contact Form</h1>
      <input
        className="input"
        placeholder="Name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      {/* value={name} 绑定 name 变量,onChange={(e) => setName(e.target.value)} 更新状态 */}
      <input
        className="input"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        className="input"
        placeholder="Website"
        value={website}
        onChange={(e) => setWebsite(e.target.value)}
      />
      <button className="btn" type="submit">
        Add
      </button>
    </form>
  );
}

Screenshot 2025-02-01 at 20.18.25