Ошибка MERN Stack — TypeError: не удается преобразовать undefined или null в object

#javascript #node.js #reactjs #express #mern

#javascript #node.js #reactjs #выражать #mern #выразить

Вопрос:

Я следую вместе со стеком MERN от Traversy Media, от начала до конца. Я пытаюсь настроить часть входа в приложение. Я убедился, что мой код точно такой же, как у него. Но я получаю ошибку TypeError: Cannot convert undefined or null to object , а он нет.

Я читал на github, и люди сказали, что это проблема с reactstrap, что-то связанное с зависимостями? Это выше моей головы. Я попытался запустить npm ci, я попытался изменить файл json-package-lock, я попытался создать файл npm-shrinkwrap и т.д., Как описано здесьhttps://github.com/reactstrap/reactstrap/issues/1373 и здесь https://github.com/reactstrap/reactstrap/issues/1374 но пока ничего не работает, это выдает точно такую же ошибку.

authActions.js:

 import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";

import { GET_ERRORS, SET_CURRENT_USER } from "./types";

// Register User
export const registerUser = (userData, history) => dispatch => {
  axios
    .post("/api/users/register", userData)
    .then(res => history.push("/login"))
    .catch(err =>
      dispatch({
        type: GET_ERRORS,
        payload: err.response.data
      })
    );
};

// Login - Get User Token
export const loginUser = userData => dispatch => {
  axios
    .post("/api/users/login", userData)
    .then(res => {
      // Save to localStorage
      const { token } = res.data;
      // Set token to ls
      localStorage.setItem("jwtToken", token);
      // Set token to Auth header
      setAuthToken(token);
      // Decode token to get user data
      const decoded = jwt_decode(token);
      // Set current user
      dispatch(setCurrentUser(decoded));
    })
    .catch(err =>
      dispatch({
        type: GET_ERRORS,
        payload: err.response.data
      })
    );
};

// Set logged in user
export const setCurrentUser = decoded => {
  return {
    type: SET_CURRENT_USER,
    payload: decoded
  };
};
  

Dashboard.js:

 import React, { Component } from "react";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { getCurrentProfile } from "../../actions/profileActions";
import Spinner from "../common/Spinner";

class Dashboard extends Component {
  componentDidMount() {
    this.props.getCurrentProfile();
  }

  render() {
    const { user } = this.props.auth;
    const { profile, loading } = this.props.profile;

    let dashboardContent;

    if (profile === null || loading) {
      dashboardContent = <Spinner />;
    } else {
      // Check if logged in user has profile data
      if (Object.keys(profile).length > 0) {
        dashboardContent = <h4>TODO: DISPLAY PROFILE</h4>;
      } else {
        // User is logged in but has no profile
        dashboardContent = (
          <div>
            <p className="lead text-muted">Welcome {user.name}</p>
            <p>You have not yet setup a profile, please add some info</p>
            <Link to="/create-profile" className="btn btn-lg btn-info">
              Create Profile
            </Link>
          </div>
        );
      }
    }

    return (
      <div className="dashboard">
        <div className="container">
          <div className="row">
            <div className="col-md-12">
              <h1 className="display-4">Dashboard</h1>
              {dashboardContent}
            </div>
          </div>
        </div>
      </div>
    );
  }
}

Dashboard.propTypes = {
  getCurrentProfile: PropTypes.func.isRequired,
  auth: PropTypes.object.isRequired,
  profile: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  profile: state.profile,
  auth: state.auth
});

export default connect(
  mapStateToProps,
  { getCurrentProfile }
)(Dashboard);
  

ожидается: загружается страница, на которую я могу войти, а затем, когда я вхожу в систему, на ней должно быть написано «Добро пожаловать, user.name . Вы еще не настроили профиль, пожалуйста, добавьте некоторую информацию»

фактические результаты:

 TypeError: Cannot convert undefined or null to object
Dashboard.render
src/components/dashboard/Dashboard.js:23

  20 |   dashboardContent = <Spinner />;
  21 | } else {
  22 |   // Check if logged in user has profile data
> 23 |   if (Object.keys(profile).length > 0) {
     | ^  24 |     dashboardContent = <h4>TODO: DISPLAY PROFILE</h4>;
  25 |   } else {
  26 |     // User is logged in but has no profile
  

Кроме того, если я ненадолго оставлю его в покое, если я вернусь к нему, он загрузит страницу, на которой я могу войти, но когда я использую информацию о входе для пользователя, которого я зарегистрировал в базе данных, он выдает мне эту ошибку:

 (anonymous function)
src/actions/authActions.js:39
  36 |     .catch(err =>
  37 |       dispatch({
  38 |         type: GET_ERRORS,
> 39 |         payload: err.response.data
  40 |       })
  41 |     );
  42 | };
  

И когда я перезагружаю, он возвращается, выдавая мне другую ошибку, показанную выше ( TypeError: Cannot convert undefined or null to object ошибка)

Ответ №1:

В вашем коде есть несколько проблем. Наиболее очевидным является то, как вы проверяете profile :

if (profile === null || loading)

Я считаю, что происходит то, что profile устанавливается как undefined и loading устанавливается как false , и, следовательно, он передает if инструкцию.

Вместо этого profile изначально установите значение как пустой объект {} . Затем вы можете использовать isEmpty() функцию Lodash, чтобы проверить, по-прежнему ли она пуста. Это также сохранит вашу propTypes проверку как 1: 1. Если это объект, он остается объектом. Если это строка, она остается строкой и так далее. Снова сохраните его 1: 1.

Кроме того, при проверке реквизитов, опишите shape из object . Когда-нибудь в будущем вам больше всего понравится use eslint , и он выдаст ошибки об использовании общих дескрипторов, таких как Proptypes.array и Proptypes.object . Хотя это может показаться излишним, оно может выявить ошибки внутри вашего объекта, если свойство отличается от описанной формы.

Рабочий codesandbox:

Редактировать реструктурированное приложение


Этот пример включает в себя некоторое расширенное / промежуточное использование кода, поэтому я бы предположил, что некоторые из них не будут иметь смысла. Если у вас есть вопросы, не стесняйтесь задавать.

В этом примере кода используются: описание объекта ES6, функции жирной стрелки с упрощенными возвратами, свойства класса fat arrow, оператор распространения и троичный оператор.

Переработанная и упрощенная версия вашего кода…

контейнеры / панель мониторинга

 import isEmpty from "lodash/isEmpty";
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { getCurrentProfile } from "../../actions/profileActions";
import DisplayUser from "../../components/DisplayUser";
import DisplaySignUp from "../../components/DisplaySignUp";
import Spinner from "../../components/Spinner";

// using a PureComponent because we're not utilizing state, 
// but we're utilizing the "componentDidMount" lifecycle
class Dashboard extends PureComponent { 
  componentDidMount = () => {
    this.props.getCurrentProfile(1);
  }

  // the below can be read like so:
  // if "isLoading" is true...  then show a spinner
  // else if "currentUser" is not empty... then display the user details
  // else show a signup message
  render = () => (
    this.props.isLoading ? (    
      <Spinner />  
    ) : !isEmpty(this.props.currentUser) ? ( 
      <DisplayUser currentUser={this.props.currentUser} /> /
    ) : (
      <DisplaySignUp />  
    )
}

// describing the shape of the "currentUser" object 
// notice that there aren't any required declarations 
// within the object itself because "currentUser" is initially 
// an empty object; however, when it's not empty, it should 
// follow this structure
Dashboard.propTypes = {
  getCurrentProfile: PropTypes.func.isRequired,
  currentUser: PropTypes.shape({ 
    id: PropTypes.number,
    name: PropTypes.string,
    username: PropTypes.string,
    email: PropTypes.string,
    address: PropTypes.shape({
      street: PropTypes.string,
      suite: PropTypes.string,
      city: PropTypes.string,
      zipcode: PropTypes.string,
      geo: PropTypes.objectOf(PropTypes.string)
    }),
    phone: PropTypes.string,
    website: PropTypes.string,
    company: PropTypes.objectOf(PropTypes.string)
  }).isRequired,
  isLoading: PropTypes.bool.isRequired
};

export default connect(
  state => ({
    currentUser: state.profile.currentUser,
    isLoading: state.profile.isLoading
  }),
  { getCurrentProfile }
)(Dashboard);
  

редукторы /profileReducer

 import * as types from "../types";

const initialState = {
  currentUser: {}, // setting currentUser initially as an empty object
  isLoading: true // setting isLoading initially as a boolean
};

export default (state = initialState, { type, payload }) => {
  switch (type) {
    case types.SET_SIGNEDIN_USER:
      return {
        ...state,
        currentUser: { ...payload },
        isLoading: false
      };
    default:
      return state;
  }
};