我需要经常在我的网站上使用考勤数据,所以我将其作为实用程序如下:
import React, {useState, useEffect, useMemo,useCallback, createContext} from 'react';
import { sendJSONRequest,getRequest } from '../utility/sendJson';
const AttendenceContext = createContext()
const AttendenceProvider = ({ children,type, grade ="" , setGrade = "" }) =>{
console.log("AttendenceProvider rendered, type:", type);
const [list,setList] = useState([])
const [attendence, setAttendence] = useState([])
const [loading, setLoading] = useState(true)
const fetchData = useCallback(async()=>{
if(type === "Teacher"){
let teacherData = await getRequest("http://localhost:5000/api/getTeacher");
teacherData = Object.values(teacherData.data).flat();
let attendData = await sendJSONRequest("http://localhost:5000/api/getAttendence", {type : type});
attendData =Object.values(attendData.data).flat()
processData(teacherData, attendData)
}
else if (type === "Student"){}
},[type])
const processData = async(data,attendenceData)=>{
setAttendence(attendenceData)
const newData = data.map((teacher) => {
const check = attendenceData.some((attendence) => {
return attendence.attendeId === teacher._id;
})
? "Absent"
: "Present";
return {
name: teacher.name,
attendeId: teacher._id,
status: check,
};
}, []);
await setList(newData);
await setLoading(false)
}
const updateGrade = (newGrade) => {
setGrade(newGrade);
};
useEffect(() => {
fetchData()
}, [fetchData]);
const Data = useMemo(()=>{
return {list,
attendence,
updateGrade}
},[list,attendence])
return (
<AttendenceContext.Provider value={Data}>
{!loading ? children : <div>Loading....</div>}
</AttendenceContext.Provider>
)
}
export { AttendenceContext, AttendenceProvider };
我在 TeacherAttend 组件中使用它,如下所示:
import React, {useState,useEffect,useCallback,useRef, useContext} from 'react';
import {Attendance} from '../../layoutattendance';
import { sendJSONRequest,getRequest } from '../../../utility/sendJson';
import {AttendenceContext} from '../../../utility/AttendenceContext';
const Teacherattend = React.memo(() => {
const { list, attendence } = useContext(AttendenceContext);
const [teacherList,setTeacherList] = useState(list)
const [AttendenceData,setAttendenceData] = useState(attendence)
const handleStatus = (attendeId)=>{
const newTeacher = teacherList.map(data=>{
if (data.attendeId === attendeId) {
return {...data,
name : data.name,
attendeId : attendeId ,
status : data.status === "Present" ? "Absent" : "Present"
}
}
else
return data
})
setTeacherList(newTeacher)
}
const handleCilck = async () => {
if (teacherList.length === 0) {
console.log("No Teachers to Mark Attendance");
return;
}
const data = {
teacher : teacherList.map(({name , ...rest})=> rest),
attendence : AttendenceData
}
try {
let newAttendence = await sendJSONRequest("http://localhost:5000/portal/mark/attendence", data);
console.log(newAttendence)
if( newAttendence.data.deleted &&newAttendence.data.deleted.length !== 0){
setAttendenceData((perv)=>{
return perv.filter(person =>{
return !newAttendence.data.deleted.includes(person.attendeId)
})
})
}
if(newAttendence.data.added &&newAttendence.data.added.length !== 0){
setAttendenceData(perv => [
...perv,
...newAttendence.data.added
])
}
} catch (error) {
console.error("Error:", error);
}
};
return (
<div className="py-6 px-2 lg:px-4 bg-white shadow-md rounded-lg">
{list ||list.length !== 0 ? <Attendance name="Teacher Attendance" data={teacherList} onStatusChange={handleStatus}/> : "Loading.."}
<div className="flex justify-end mt-6">
<button
type="submit"
onClick={handleCilck}
className="motion-preset-shrink py-3 px-6 text-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-300"
>
Submit
</button>
</div>
</div>
);
});
export default Teacherattend;
以下是我的main.jsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import Get from "./components/Account/GetStarted.jsx";
import Admin from "./components/Admin/Admin.jsx";
import App from "./App.jsx";
import AdminTeachers from "./components/Admin/components/teachers.jsx";
import Students from "./components/Admin/components/students.jsx";
import Teachers from "./components/Teacher/teachers.jsx";
import Attendance from "./components/Teacher/components/attendance";
import Addstudent from "./components/Admin/components/studentsinfo.jsx"
import Teachersattend from "./components/Admin/components/Teacherattend.jsx"
import Removestudent from "./components/Admin/components/removestudent.jsx"
import Addteacher from "./components/Admin/components/addTeacher.jsx"
import ClassesLists from "./components/Headmaster/components/ClassesLists.jsx";
import Home from './components/Teacher/components/home.jsx'
import PrivateRoute from "./utility/PrivateRoute.jsx";
import { AuthProvider } from "./utility/AuthContext.jsx";
import Class from "./components/Admin/components/class.jsx";
import View from "./components/Admin/components/view.jsx";
import Substitution from "./components/Admin/components/Substitution.jsx";
import Classadd from "./components/Admin/components/classadd.jsx";
import Manage from "./components/Admin/components/manage.jsx";
import Time from "./components/Admin/components/time.jsx";
import {AttendenceProvider} from "./utility/AttendenceContext.jsx";
const router = createBrowserRouter([
// Public Routes
{ path: "/", element: <App /> },
{ path: "/login", element: <Get /> },
// Headmaster Routes
{
path: "/headmaster",
element: (
<PrivateRoute roles={["headmaster"]}>
<Home />
</PrivateRoute>
),
children: [
{
path: "students",
element: (
<PrivateRoute roles={["headmaster"]}>
<ClassesLists />
</PrivateRoute>
),
},
],
},
// Admin Routes
{
path: "/admin",
element: (
<PrivateRoute roles={["Admin"]}>
<Admin />
</PrivateRoute>
),
children: [
{
path: "", // Important: Empty string for index route
element: (
<AttendenceProvider type="Teacher">
<Teachersattend />
</AttendenceProvider>
),
},
{ path: "teachers", element:<AdminTeachers/> },
{ path: "students", element:<Students /> },
{ path: "students/add", element: <Addstudent /> },
{ path: "teachers/add", element: <Addteacher />},
{ path: "students/remove", element:<Removestudent />},
{ path: "class", element:<Class />},
{ path: "class/view", element:<View /> },
{ path: "class/add", element:<Classadd /> },
{ path: "teachers/substitution", element:<Substitution /> },
{ path: "teachers/manage", element:<Manage /> },
{ path: "time", element:<Time />},
],
},
// Teacher Routes
{
path: "/teachers",
element: (
<PrivateRoute roles={["Teacher"]}>
<Teachers />
</PrivateRoute>
),
children: [
{
path: "/teachers",
element: (
<PrivateRoute roles={["Teacher"]}>
<Home />
</PrivateRoute>
),
},
{
path: "/teachers/attendance",
element: (
<PrivateRoute roles={["Teacher"]}>
<Attendance />
</PrivateRoute>
),
},
],
},
// Unauthorized Route
{ path: "/unauthorized", element: <div>Unauthorized Access</div> },
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
</StrictMode>
);
我还测试了我的身份验证提供程序,但没有发现任何问题。
我的身份验证上下文:
import React, { createContext, useState, useEffect, useCallback, useMemo, useContext } from "react";
const AuthContext = createContext(null);
const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({
isAuthenticated: false,
roles: [],
loading: true,
});
const fetchAuth = useCallback(async () => {
try {
const response = await fetch("http://localhost:5000/portal/auth", {
method: "GET",
credentials: "include",
});
if (response.status === 200) {
const data = await response.json();
if (
!auth.isAuthenticated ||
!arraysAreEqual(auth.roles, data.role)
) {
setAuth({
isAuthenticated: true,
roles: data.role,
loading: false,
});
}
} else {
// Efficient check before setting state
if (auth.isAuthenticated || !auth.loading) {
setAuth({ isAuthenticated: false, roles: [], loading: false });
}
}
} catch (error) {
console.error("Error fetching auth data:", error);
// Efficient check before setting state
if (auth.isAuthenticated || !auth.loading) {
setAuth({ isAuthenticated: false, roles: [], loading: false });
}
}
}, [auth]);
useEffect(() => {
fetchAuth();
}, [fetchAuth]);
const login = useCallback((userData) => {
setAuth({ isAuthenticated: true, roles: userData.role });
}, []); // Empty dependency array
const logout = useCallback(() => {
setAuth({ isAuthenticated: false, roles: [], loading: false });
}, []);
const contextValue = useMemo(() => ({
auth,
login,
logout,
}), [auth, login, logout]);
return (
<AuthContext.Provider value={contextValue}>
{!auth.loading ? children : <div>Loading...</div>}
</AuthContext.Provider>
);
};
// Helper function for shallow comparison of arrays
function arraysAreEqual(arr1, arr2) {
if (!arr1 || !arr2 || arr1.length !== arr2.length) return false; // Handle null/undefined
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};
export { AuthContext, AuthProvider, useAuth };
当我重新导航到该组件时,我不想重新获取。但经过我的调试,现在它的获取次数超过了两次。但为什么呢?
当用户第一次点击 /admin 路由时应该获取它。
您正在使用
AttendanceProvider
作为包装组件并在特定的 "/admin"
路线上进行渲染。每次匹配该路线时,都会安装 element
组件并运行初始效果,例如获取数据,当您离开该路线时,element
组件将被卸载。导航回 "/admin"
将安装 AttendanceProvider
并再次获取数据。
我建议将
AttendanceProvider
推到 ReactTree 的更高位置,以便它仅安装一次,并提供获取的数据 和 回调来获取它。后代组件可以检查数据是否已经加载,如果尚未加载则获取数据。或者回调函数可以更智能一些,仅在尚未获取/加载数据时才获取数据。
import React, {
useContext,
useState,
useEffect,
useMemo,
useCallback,
createContext
} from 'react';
import { sendJSONRequest, getRequest } from '../utility/sendJson';
const AttendanceContext = createContext({
attendance: null,
fetchData: () => {},
isLoading: false,
list: null,
updateGrade: () => {},
});
export const useAttendance = () => useContext(AttendanceContext);
export const AttendanceProvider = ({
children,
grade = "",
setGrade = ""
}) =>{
const [list, setList] = useState(null);
const [attendance, setAttendance] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const fetchData = useCallback(async (type) => {
// If data fetched already, return early
if (list !== null && attendance !== null) return;
const processData = (data, attendanceData) => {
setAttendance(attendanceData);
setList(data.map((teacher) => ({
name: teacher.name,
attendeId: teacher._id,
status: attendanceData.some(
({ attendeId }) => attendeId === teacher._id
)
? "Absent"
: "Present",
}));
};
try {
setIsLoading(true);
switch(type) {
case "Teacher":
const flattenResponseData = ({ data }) => Object.values(data).flat();
const [teacherData, attendData] = await Promise.all([
getRequest("http://localhost:5000/api/getTeacher")
.then(flattenResponseData),
sendJSONRequest(
"http://localhost:5000/api/getAttendance",
{ type }
)
.then(flattenResponseData),
]);
processData(teacherData, attendData);
break;
case "Student":
default:
// nothing
}
} catch(error) {
// set the states to empty arrays
setAttendance([]);
setList([]);
} finally {
setIsLoading(false);
}
}, [processData]);
const value = useMemo(() => {
return {
attendance,
fetchData,
isLoading,
list,
updateGrade: setGrade
}
}, [attendance, fetchData, isLoading, list, setGrade]);
return (
<AttendanceContext.Provider value={value}>
{children}
</AttendanceContext.Provider>
);
}
创建一个包装器来调用
fetchData
组件提供的 AttendanceProvider
回调。
import { useAttendance } from '../../../utility/AttendanceContext';
export const AttendanceWrapper = ({ children, type }) => {
const { fetchData, isLoading } = useAttendance();
useEffect(() => {
fetchData(type);
}, [fetchData, type]);
return isLoading ? <div>Loading....</div> : children;
};
用新的
TeachersAttend
组件包裹 AttendanceWrapper
,并在路由器周围的 ReactTree 中将 AttendanceProvider
渲染到更高的位置。
// Admin Routes
{
path: "/admin",
element: (
<PrivateRoute roles={["Admin"]}>
<Admin />
</PrivateRoute>
),
children: [
{
index: true,
element: (
<AttendanceWrapper type="Teacher">
<TeachersAttend />
</AttendanceWrapper>
),
},
...
],
},
createRoot(document.getElementById("root")).render(
<StrictMode>
<AuthProvider>
<AttendanceProvider>
<RouterProvider router={router} />
</AttendanceProvider>
</AuthProvider>
</StrictMode>
);
const TeacherAttend = () => {
const { attendance, list } = useAttendance();
...
};