开发者问题收集

未捕获的类型错误:无法读取 Reactjs 中未定义的属性“map”-redux

2021-05-30
210

我正在参与 MongoDB、Express、React、Node (MERN) 项目。我遇到了“在更改 redux 文件以实现从 Material UI/core <CircularProgress /> 的加载效果后,无法读取未定义的属性‘map’”的问题

我尝试过以不同的方式通过 useSelector 访问数据,甚至使用 shallowEqual 方法。我也尝试在 DashBoardAdmin 内调用 getStudents()。同时也尝试使用 useEffect 来分派带有依赖项数组的 (getStudents())。到目前为止,一切都没有奏效。然后尝试在 chrome 的检查部分进行调试,我发现在第一次重新加载页面时,碰巧从 action.payload 上的后端获取数据,但无法将其作为一个整体填充到状态中。这可能是 useSelector 获取空数组并提供“无法读取未定义的属性‘map’”的原因

我假设,在状态中引入对象后,reducers 的 students.js 文件之后出现了问题。我正在尽力调试。

我的 index.js 文件:

import React from "react";
import ReactDOM from "react-dom";
import "./Styles/index.css";
import App from "./App";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";

import { reducers } from "./redux/reducers/index";

const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
    : compose;

const enhancer = composeEnhancers(compose(applyMiddleware(thunk)));
const store = createStore(reducers, enhancer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

我的 app.js 文件:

import React, { useEffect, useState } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import "./Styles/App.css";
import AdminSignIn from "./Pages/AdminSignIn";
import DashBoardAdmin from "./Pages/Admin";
import NavbarAdmin from "./Navbars/NavbarAdmin";
import BottomNavbar from "./Navbars/bottomNavbar";
import { useDispatch } from "react-redux";
import { Typography } from "@material-ui/core";
import { NotFound } from "./Not_Found/NotFound";
import { getStudents } from "./redux/actions/studentAction";

function App() {
  const user = JSON.parse(localStorage.getItem("account"));
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(getStudents());
  }, [dispatch]);

  return (
    <>
      <Router>
      {user?.result?._id ? (
        <NavbarAdmin />
      ) : (
        <Typography variant="h2">{"Fetch"} Organization Name</Typography>)}
        <Switch>
          <Route path="/" exact>
            <AdminSignIn />
          </Route>
          <Route path="/dashboard" exact>
            <DashBoardAdmin />
          </Route>
 
          <Route >
            <NotFound />
          </Route>
        </Switch>
        <BottomNavbar />
      </Router>
    </>
  );
}

export default App;

我的 DashBoardAdmin.js 文件:

import { Box, Button, Card, CardHeader, Chip, CircularProgress, Divider, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, Tooltip} from "@material-ui/core";
import { Link } from 'react-router-dom'
import ArrowRightIcon from "@material-ui/icons/ArrowRight";
import moment from "moment";
import PerfectScrollbar from "react-perfect-scrollbar";
import { useSelector } from "react-redux";

const DashBoardAdmin = () => {
  const { students, isLoading } = useSelector((state) => state.students);

  return (
    <div className="padding-grid">
      <Card>
        <CardHeader title="Latest updates on students" />
        <Divider />
        <PerfectScrollbar>
          <Box sx={{ minWidth: 800 }}>
            <Table>
              <TableHead>
                <TableRow>
                  <TableCell>Roll Number</TableCell>
                  <TableCell>Name of student</TableCell>
                  <TableCell sortDirection="desc">
                    <Tooltip enterDelay={300} title="Sort">
                      <TableSortLabel active direction="desc">
                        Date of Admission
                      </TableSortLabel>
                    </Tooltip>
                  </TableCell>
                  <TableCell>Status</TableCell>
                </TableRow>
              </TableHead>
              {isLoading ? (
                <CircularProgress />
              ) : (
                <TableBody>
                  {students.map((stu) => (
                    <TableRow hover key={stu.id}>
                      <TableCell>{stu.rollNumber}</TableCell>
                      <TableCell>{stu.firstName}  {" "} {stu.lastName} + {" "} {stu.surname}</TableCell>
                      <TableCell>
                        {moment(stu.createdAt).format("DD/MM/YYYY")}
                      </TableCell>
                      <TableCell>
                        <Chip color="primary" label={stu.status} size="small" />
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              )}
            </Table>
          </Box>
        </PerfectScrollbar>
        <Box
          sx={{
            display: "flex",
            justifyContent: "flex-end",
            p: 2,
          }}>
            <Link to="/students-info">
          <Button
            color="primary"
            endIcon={<ArrowRightIcon />}
            size="small"
            variant="text">
            View all
          </Button>
          </Link>
        </Box>
      </Card>
    </div>
  );
};

export default DashBoardAdmin;

我的 redux studentAction.js 文件:

import { FETCH_STUDENTS, START_LOADING, END_LOADING } from "../constants/actionTypes";
import * as api from "../api/index.js";

export const getStudents = () => async (dispatch) => {
  try {
    dispatch({ type: START_LOADING })
    const { data } = await api.fetchStudents();
    dispatch({ type: FETCH_STUDENTS, payload: data });
    dispatch({ type: END_LOADING})
  } catch (error) {
    console.log(error);
  }
};

我的 API index.js 文件:

import axios from "axios";

const API = axios.create({ baseURL: "http://localhost:5000" });

API.interceptors.request.use((req) => {
  if (localStorage.getItem("account")) {
    req.headers.Authorization = `Bearer ${
      JSON.parse(localStorage.getItem("account")).token
    }`;
  }

  return req;
});

export const fetchStudents = () => API.get("/students-info");

我的 students.js 的 Reducer,最好的猜测是这里出了问题,或者它是在我包含 isLoading 之后开始的:

import { FETCH_STUDENTS, START_LOADING, END_LOADING } from "../constants/actionTypes";

function students(state = { isLoading: true, students: [] }, action) {
  switch (action.type) {
    case START_LOADING:
      return { ...state, isLoading: true };
    case END_LOADING:
      return { ...state, isLoading: false };
  
    case FETCH_STUDENTS:
      return { ...state, students: action.payload.data };
    
    default:
      return state;
  }
}

export default students;

index.js 合并 Reducer 文件:

import { combineReducers } from "redux";

import students from "./students";
import auth from "./auth";

export const reducers = combineReducers({ students, auth });

我收到的错误是:

Uncaught TypeError: Cannot read property 'map' of undefined
    at DashBoardAdmin (DashBoardAdmin.js:51)
    at renderWithHooks (react-dom.development.js:14985)
    at updateFunctionComponent (react-dom.development.js:17356)
    at beginWork (react-dom.development.js:19063)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at beginWork$1 (react-dom.development.js:23964)
    at performUnitOfWork (react-dom.development.js:22776)
    at workLoopSync (react-dom.development.js:22707)
    at renderRootSync (react-dom.development.js:22670)
    at performSyncWorkOnRoot (react-dom.development.js:22293)
    at react-dom.development.js:11327
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at flushSyncCallbackQueueImpl (react-dom.development.js:11322)
    at flushSyncCallbackQueue (react-dom.development.js:11309)
    at batchedUpdates$1 (react-dom.development.js:22387)
    at Object.notify (Subscription.js:19)
    at Subscription.notifyNestedSubs (Subscription.js:90)
    at Subscription.handleChangeWrapper (Subscription.js:95)
    at Object.dispatch (redux.js:297)
    at dispatch (<anonymous>:3856:17)
    at index.js:11
    at dispatch (redux.js:659)
    at studentAction.js:35

另一个错误:

Warning: validateDOMNesting(...): <div> cannot appear as a child of <table>.
    at div
    at CircularProgress (http://localhost:4000/static/js/vendors~main.chunk.js:80761:23)
    at WithStyles (http://localhost:4000/static/js/vendors~main.chunk.js:119309:31)
    at table
    at Table (http://localhost:4000/static/js/vendors~main.chunk.js:102171:23)
    at WithStyles (http://localhost:4000/static/js/vendors~main.chunk.js:119309:31)
    at div
    at StyledComponent (http://localhost:4000/static/js/vendors~main.chunk.js:119080:28)
    at div
    at ScrollBar (http://localhost:4000/static/js/vendors~main.chunk.js:231982:5)
    at div
    at Paper (http://localhost:4000/static/js/vendors~main.chunk.js:94231:23)

我正在使用 students.js 文件中 redux 的简单语法从后端获取数据:

import { FETCH_STUDENTS } from "../constants/actionTypes";
export default (students = [], action) => {
   switch (action.type) {
     case FETCH_STUDENTS:
       return action.payload;
 default:
       return students;
   }
 };

需要获取实现 isLoading 或 START_LOADING / END_LOADING 分派到 UI 的替代方法

1个回答

好的,看了你的 app.js 之后,我发现你正在使用 thunk,而且我认为你必须使用函数,而不是直接传递对象。

另外,我添加了一个操作来获取错误,以防万一

studentAction.js

import {
 FETCH_STUDENTS,
 START_LOADING,
 END_LOADING,
} from "../constants/actionTypes";
import * as api from "../api";

const startLoading = () => {
 return {
  type: START_LOADING,
 };
};

const fetchStudents = (data) => {
 return {
  type: FETCH_STUDENTS,
  data,
 };
};

const endLoading = (error) => {
 return {
  type: END_LOADING,
  error,
 };
};

export const getStudents = () => {
 return async (dispatch) => {
  dispatch(startLoading());

  try {
   const { data } = await api.fetchStudents()
 
   dispatch(fetchStudents(data))

  } catch (error) {
   dispatch(endLoading(error.message));
  }
 };
};

students.js

import {
 FETCH_STUDENTS,
 START_LOADING,
 END_LOADING,
} from "../../constants/actionTypes";

const initialState = {
 isLoading: false,
 error: "",
 students: [],
};

function students(state = initialState, action) {
 switch (action.type) {
  case START_LOADING:
   return { ...state, isLoading: true };
  case END_LOADING:
   return { ...state, isLoading: false, students: [], error: action.error };

  case FETCH_STUDENTS:
   return {
    ...state,
    isLoading: false,
    students: action.data,
   };

  default:
   return state;
 }
}

export default students;

你的 app.js 和 index.js 对我来说是正确的

并且在你的 Admin.js 中你可以使用此条件

import { useSelector } from "react-redux";

const DashBoardAdmin = () => {
 const { students, isLoading, error } = useSelector((state) => state.students);

 return (
  <>
   {error === "" && (
    <div className="padding-grid">
     ...
     {isLoading || !students.length > 0 ? (
      <h1>loading...</h1>
     ) : (
      <div>
       {students.map((stu) => (
        <p key={stu.id}>{JSON.stringify(stu)}</p>
       ))}
      </div>
     )}
     ...
    </div>
   )}
  </>
 );
};

export default DashBoardAdmin;
Daphaz
2021-05-30