React ã使ã£ã¦å¤é¨APIãã³ã¼ã«ããåå¾ããã¬ã¹ãã³ã¹ãç»é¢ã«åæ ããç°¡åãªãµã³ãã«ã§ãã
ä¸é£ã®ãµã³ãã«ã³ã¼ã㯠ãã¡ã ã«ããã·ã¥ãã¦ãã¾ãã
Â
ãµã³ãã«å®è¡ãã£ããã£
Â
Â
å®è¡ç°å¢ã«ã¤ãã¦
ä¸è¨ã®ç°å¢ã§åããã¦ãã¾ãã
- Ubuntu 20.04.4 LTS (Focal Fossa)
- go version go1.21.6 linux/amd64
- Node v16.20.2
- yarn 1.22.21
- React 18.2.0
- TypeScript 4.9.5
Â
å¤é¨APIã«ã¤ãã¦
ãGoè¨èªãgo/gin ã§ç°¡åãªREST API ãä½æ ã«ã¦ä½æããAPI ã使ç¨ãã¦ãã¾ãã
Â
äºåæºå
docker-compose.yml
version: '3'
services:
 postgres:
  image: 'postgres:14'
  container_name: postgres_gin_rest
  environment:
   POSTGRES_USER: user
   POSTGRES_PASSWORD: password
   POSTGRES_DB: sample
   TZ: Asia/Tokyo
   - '5432:5432'
  volumes:
Â
docker-compose up -dÂ
ã³ãã³ã㧠PostgreSQL ãèµ·åããå¾ã
docker exec -it postgres_gin_rest psql -U user -d sample
ã³ãã³ãã§DBã«æ¥ç¶ãã¦ãä¸è¨CREATEæã§Userãã¼ãã«ãä½æãã¦ããã¾ãã
CREATE TABLE "user" (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255),
  age INT
);
Â
React ã使ã£ã¦å¤é¨APIãã³ã¼ã«ãµã³ãã«å®è£
ã¢ãã«å®ç¾©
api/model/User.ts
type User = {
 Id?: number;
 Name: String;
 Age: number;
};
export default User;
- APIã¬ã¹ãã³ã¹ãåããUserã¢ãã«ãå®ç¾©
Â
APIã³ã¼ã«å¦ç
api/UserApi.ts
import User from "./model/User";
export const fetchUserData = async (id: number): Promise<User | null> => {
 try {
  if (!response.ok) {
   console.error("Error fetching user data. Status:", response.status);
   return null;
  }
  const responseData: { user: User } = await response.json();
  const userData: User = responseData.user;
  return userData;
 } catch (error) {
  console.error("Error fetching user data:", error);
  return null;
 }
};
export const fetchUserDatas = async (): Promise<User | null> => {
 try {
  if (!response.ok) {
   // ã¬ã¹ãã³ã¹ãæåã§ãªãå ´åã®å¦ç
   console.error("Error fetching user data. Status:", response.status);
   return ;
  }
  const { users }: { users: User } = await response.json();
  return users || ;
 } catch (error) {
  console.error("Error fetching user data:", error);
  return null;
 }
};
export const RegisterUser = async (userData: User): Promise<User | null> => {
 try {
   method: "POST",
   headers: {
    "Content-Type": "application/json",
   },
   body: JSON.stringify(userData),
  });
  if (!response.ok) {
   console.error("Error registering user. Status:", response.status);
   return null;
  }
  const registeredUser: User = await response.json();
  return registeredUser;
 } catch (error) {
  console.error("Error registering user:", error);
  return null;
 }
};
export const UpdateUser = async (userData: User): Promise<User | null> => {
 try {
  const response = await fetch(apiUrl, {
   method: "PUT",
   headers: {
    "Content-Type": "application/json",
   },
   body: JSON.stringify(userData),
  });
  if (!response.ok) {
   console.error("Failed to update user");
   return null;
  }
  const updatedUser: User = await response.json();
  return updatedUser;
 } catch (error) {
  console.error("Error updating user:", error);
  return null;
 }
};
export const deleteUser = async (userId: number): Promise<boolean> => {
 try {
  const response = await fetch(apiUrl, {
   method: "DELETE",
   headers: {
    "Content-Type": "application/json",
   },
  });
  if (!response.ok) {
   console.error("Failed to delete user");
   return false;
  }
  return true;
 } catch (error) {
  console.error("Error deleting user:", error);
  return false;
 }
};
- å¤é¨APIã®CRUDã«å¯¾å¿ããå¦çãããã«è¨è¿°ãã¦ãã¾ãã
Â
ããã¾ã§ã§ã¢ãã«ã¨APIå¼ã³åºãå¦çãã§ããã®ã§ãç»é¢ã³ã³ãã¼ãã³ããä½æãã¦ããã¾ãã
Â
ãããç»é¢
component/UserComponent.tsx
import { useEffect, useState } from "react";
import User from "../api/model/User";
import { deleteUser, fetchUserDatas } from "../api/UserApi";
import "./UserComponent.css";
import { Link } from "react-router-dom";
const UserComponent: React.FC = () => {
 const [userList, setUserList] = useState<User | null>(null);
 const [isDeleteDialogOpen, setDeleteDialogOpen] = useState<boolean>(false);
 const [deletingUserId, setDeletingUserId] = useState<number | null>(null);
 useEffect(() => {//â
  const fetchData = async () => {
   const data = await fetchUserDatas();
   setUserList(data);
  };
  fetchData();
 }, );
 const handleDeleteClick = (userId: number) => {
  setDeletingUserId(userId);
  setDeleteDialogOpen(true);
 };
 const handleDeleteConfirm = async () => {
  if (deletingUserId !== null) {
   try {
    const isDeleted = await deleteUser(deletingUserId);
    if (isDeleted) {
     setDeleteDialogOpen(false);
     const updatedData = await fetchUserDatas();
     setUserList(updatedData);
    } else {
     console.error("Error deleting user");
    }
   } catch (error) {
    console.error("Error deleting user data:", error);
   }
  }
 };
 const handleDeleteCancel = () => {
  setDeletingUserId(null);
  setDeleteDialogOpen(false);
 };
 return (
  <div>
   {userList ? (
    <div>
     <h2>User List</h2>
     <table className="user-table">
      <thead>
       <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
       </tr>
      </thead>
      <tbody>
       {userList.map*1}
      </tbody>
     </table>
     <div>
      <Link to={"/user/register"}>æ°è¦ã¦ã¼ã¶ã¼è¿½å </Link>//â
     </div>
     {/* åé¤ãã¤ã¢ãã° */}
     {isDeleteDialogOpen && (
      <div className="delete-dialog">
       <p>æ¬å½ã«åé¤ãã¾ããï¼</p>
       <button onClick={handleDeleteConfirm}>ã¯ã</button>
       <button onClick={handleDeleteCancel}>ããã</button>
      </div>
     )}
    </div>
   ) : (
    <p>ã¦ã¼ã¶ã¼ãã¼ã¿ãèªã¿è¾¼ãã§ãã¾ã...</p>//â
   )}
  </div>
 );
};
export default UserComponent;
- å¯ä½ç¨ãã㯠(useEffect) ã使ã£ã¦ã³ã³ãã¼ãã³ãèªã¿è¾¼ã¿å®äºå¾ã« fetchUserDatas() ãå¼ã³åºããAPIããã¦ã¼ã¶ã¼ã®ä¸è¦§ãåå¾ãã¾ã
- ãã¼ã¿ã®èªã¿è¾¼ã¿ãå®äºããã¾ã§ã¯ããã¦ã¼ã¶ã¼ãã¼ã¿ãèªã¿è¾¼ãã§ãã¾ã...ãã表示ãã¦ããã¾ã
- ãæ°è¦ã¦ã¼ã¶ã¼è¿½å ãããã³ãæ´æ°ããã¯ãªãã¯ããããããããã®ç»é¢ã«é·ç§»ãã¾ã
- ãåé¤ããã¯ãªãã¯ããããåé¤ç»é¢ã«é·ç§»ãã¾ã
Â
ç»é²ç»é¢
component/user-register/UserRegister.tsx
import { useState } from "react";
import { RegisterUser } from "../../api/UserApi";
import { Link } from "react-router-dom";
const UserRegister: React.FC = () => {
 const [name, setName] = useState<string>("");//â
 const [age, setAge] = useState<number>(0);
 const [isSubmitSuccess, setIsSubmitSuccess] = useState<boolean>(false);
 const handleRegister = async () => {
  const registeredUserData = await RegisterUser({
   Name: name,
   Age: age,
  });
  if (registeredUserData) {
   setIsSubmitSuccess(true);
  }
 };
 return (
  <div>
   <div>
    <h2>User Register</h2>
    <div style={{ marginBottom: "10px" }}>
     <label>Name</label>
     <input
      type="text"
      onChange={(e) => setName(e.target.value)}
     />
    </div>
    <div style={{ marginBottom: "10px" }}>
     <label>Age</label>
     <input
      type="number"
      onChange={(e) => setAge(parseInt(e.target.value, 10))}
     />
    </div>
    <div>
     <button onClick={handleRegister} style={{ marginRight: "5px" }}>
      ç»é²
     </button>
     <Link to={"/"}>æ»ã</Link>
    </div>
    {isSubmitSuccess && <p>ç»é²ãã¾ããã</p>}
   </div>
  </div>
 );
};
export default UserRegister;
- useState ããã¯ã§ç»é¢ã®å
¥åç¶æ
ãä¿æãã¾ã
Â
æ´æ°ç»é¢
component/user-update/UserUpdate.tsx
import { useEffect, useState } from "react";
import User from "../../api/model/User";
import { UpdateUser, fetchUserData } from "../../api/UserApi";
import { Link, useParams } from "react-router-dom";
const UserUpdate: React.FC = () => {
 const { id } = useParams();//â
 const numericId: number = id ? parseInt(id, 10) : 0;
 const [user, setUser] = useState<User | null>(null);
 const [updatedName, setUpdatedName] = useState<string>("");
 const [updatedAge, setUpdatedAge] = useState<number | undefined>(undefined);
 const [isUpdateSuccess, setIsUpdateSuccess] = useState<boolean>(false);
      />
     </div>
     <div>
      <button onClick={handleUpdate} style={{ marginRight: "5px" }}>
       Update
      </button>
      <Link to={"/"}>æ»ã</Link>
     </div>
     {isUpdateSuccess && <p>æ´æ°ãã¾ããã</p>}
    </div>
   ) : (
    <p>Loading user data...</p>
   )}
  </div>
 );
};
export default UserUpdate;
- useParams ããã¯ã§URLãã©ã¡ã¼ã¿ã¼ãåå¾ãã¾ããå¾ã§è¨è¼ããã«ã¼ãã£ã³ã°è¨å®ã«ã¦ã
<Route path="user/update/:id" element={<UserUpdate />} />
ã®å®ç¾©ãè¡ã£ã¦ãããããã§æå®ãã¦ãã :id ã®å¤ãèªã¿è¾¼ãã§ãã¾ã
Â
Â
ã«ã¼ãã£ã³ã°è¨å®
import React from "react";
import logo from "./logo.svg";
import UserComponent from "./component/UserComponent";
import { Route, Router, Routes } from "react-router-dom";
import UserUpdate from "./component/user-update/UserUpdate";
import UserRegister from "./component/user-register/UserRegister";
const App: React.FC = () => {
 return (
  <Routes>
   <Route path="/" element={<UserComponent />} />
   <Route path="user/register" element={<UserRegister />} />
   <Route path="user/update/:id" element={<UserUpdate />} />
  </Routes>
 );
};
export default App;
React router ã使ç¨ãã¦è¨å®ãã¦ãã¾ãããã¹ã«å¯¾ãã¦ã©ã®ã³ã³ãã¼ãã³ããèªã¿è¾¼ãããã·ã³ãã«ã«ã«ã¼ãã£ã³ã°ãå®ç¾©ã§ãã¾ãã詳ããã¯ä¸è¨ãåç
§ãã ããã
Feature Overview v6.22.0 | React Router
Â
以ä¸ããµã³ãã«ã«ãªãã¾ãã
Â
ç»é¢UIããã¡ã¤ã«ã»ã³ã³ãã¼ãã³ãåãªã©ã®æ§æã¯ããªãéã§ã... ä»å㯠React ã§å¤é¨APIãã³ã¼ã«ãã¦ã¬ã¹ãã³ã¹ãç»é¢ã«åæ ããã¾ã§ã®ç°¡åãªæµããè¨è¼ãããã£ãã®ã§ããã®ã»ãã®è¦ç´ ã¯çãã¦ãã¾ãã
Â
        <tr key={user.Id}>
         <td>{user.Id}</td>
         <td>{user.Name}</td>
         <td>{user.Age}</td>
         <td>
          <Link to={`/user/update/${user.Id}`}>æ´æ°</Link>//â
          {" | "}
          <button onClick={() => handleDeleteClick(user.Id!)}>
           åé¤//â
          </button>
         </td>
        </tr>
      Â
  const fetchData = async () => {
   const data = await fetchUserData(numericId);
   setUser(data);
  };
  fetchData();
 }, []);
 const handleUpdate = async () => {
  if (user) {
   const updatedUserData = await UpdateUser({
    Id: numericId,
    Name: updatedName || user.Name,
    Age: updatedAge !== undefined ? updatedAge : user.Age,
   });
   if (updatedUserData) {
    setIsUpdateSuccess(true);
   }
  }
 };
 return (
  <div>
   {user ? (
    <div>
     <h2>User Update</h2>
     <p>ID: {user.Id}</p>
     <p>Name: {user.Name}</p>
     <p>Age: {user.Age}</p>
     <div style={{ marginBottom: "10px" }}>
      <label>Updated Name:</label>
      <input
       type="text"
       value={updatedName}
       onChange={(e) => setUpdatedName(e.target.value)}
      />
     </div>
     <div style={{ marginBottom: "10px" }}>
      <label>Updated Age:</label>
      <input
       type="number"
       value={updatedAge !== undefined ? updatedAge : ""}
       onChange={(e) => setUpdatedAge(parseInt(e.target.value, 10