为什么在导航回教师参加组件时再次提取出勤上下文?

问题描述 投票:0回答:1

我需要经常在我的网站上使用考勤数据,所以我将其作为实用程序如下:

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 };

当我重新导航到该组件时,我不想重新获取。但经过我的调试,现在它的获取次数超过了两次。但为什么呢?

reactjs react-router react-context
1个回答
0
投票

当用户第一次点击 /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();
  ...
};
© www.soinside.com 2019 - 2024. All rights reserved.