0

I encountered this error when trying to switch my bootcamp project code from class components to stateful functional components:

TypeError: Cannot read property 'user' of undefined
(anonymous function)
C:/Proiect curs react/src/pages/Login/Login.jsx:35
  32 | // }
  33 | 
  34 | useEffect((prevProps) => {
> 35 |  if (props.user !== prevProps.user) {
     | ^  36 |      props.history.push("/");
  37 |  }
  38 | }, [props.user, props.history]);

This occured in the Login component which is responsable for displaying an option to connect via Google - using a firebase service, and redirecting to the home page, which is what the useEffect is trying to do.

Commented below in the code is the code of the componentDidUpdate methond that did just that while the component was a class.

import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import Logo from "../../assets/images/logo.png";
import { ReactComponent as Google } from "../../assets/icons/google.svg";
import "./Login.css";
import { connect } from "react-redux";
import { loginUser } from "../../redux/actions/user";

const Login = (props) => {
    // componentDidUpdate(prevProps) {
    //     if (this.props.user !== prevProps.user) {
    //         this.props.history.push('/');
    //     }
    // }

    useEffect((prevProps) => {
        if (props.user !== prevProps.user) {
            props.history.push("/");
        }
    }, [props.user, props.history]);

    return (
        <div className="login-page">
            <Link to="/">
                <img src={Logo} alt="logo" className="mb-5" />
            </Link>
            <h1 className="h2">Login</h1>
            <p>Alege providerul cu care vrei să vrei să te loghezi:</p>
            <button
                className="btn btn-outline-dark d-flex align-items-center"
                onClick={() => props.signInWithGoogle()}
            >
                <Google className="w-50 mr-3" />
                <span className="text-nowrap">Loghează-te cu Google</span>
            </button>
        </div>
    );
};

function mapStateToProps(state) {
    return {
        user: state.user.data,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        signInWithGoogle: () => dispatch(loginUser()),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Also, below is the link to the github repository that holds the whole app.

https://github.com/vradu007/radu-shop

Please let me know if you can tell me what I did wrong - I have not studied hooks at my bootcamp, but I am using them at work, and I have been trying to get a grasp of it looking online. Thank you!

Edit: Using that answer, I got the following error:

./src/pages/Login/Login.jsx
  Line 10:17:  React Hook "useRef" is called in function "getPrevious" which is neither a React function component or a custom React Hook fun
ction     react-hooks/rules-of-hooks
  Line 11:5:   React Hook "useEffect" is called in function "getPrevious" which is neither a React function component or a custom React Hook 
function  react-hooks/rules-of-hooks

The error is reproduced whether or not I import useRef. The current component is looking like this:

import React, { useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import Logo from "../../assets/images/logo.png";
import { ReactComponent as Google } from "../../assets/icons/google.svg";
import "./Login.css";
import { connect } from "react-redux";
import { loginUser } from "../../redux/actions/user";

function getPrevious(value) {
    const ref = useRef();
    useEffect(() => {
        ref.current = value;
    });
    return ref.current;
}

const Login = (props) => {
    // componentDidUpdate(prevProps) {
    //     if (this.props.user !== prevProps.user) {
    //         this.props.history.push('/');
    //     }
    // }
    
    const { user, history } = props;
    const previous = getPrevious({ user, history });
    useEffect(
        (prevProps) => {
            if (props.user !== previous.user) {
                props.history.push("/");
            }
        },
        [props.user, props.history]
    );

    return (
        <div className="login-page">
            <Link to="/">
                <img src={Logo} alt="logo" className="mb-5" />
            </Link>
            <h1 className="h2">Login</h1>
            <p>Alege providerul cu care vrei să vrei să te loghezi:</p>
            <button
                className="btn btn-outline-dark d-flex align-items-center"
                onClick={() => props.signInWithGoogle()}
            >
                <Google className="w-50 mr-3" />
                <span className="text-nowrap">Loghează-te cu Google</span>
            </button>
        </div>
    );
};

function mapStateToProps(state) {
    return {
        user: state.user.data,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        signInWithGoogle: () => dispatch(loginUser()),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);

2 Answers2

0

Since you cant reach prevProps using useEffect an alternatively way is using useRef to hold the value and then compare.

import React, { useEffect , useRef} from "react";

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

And make this changes for your useEffect

const previous = usePrevious({props.user, props.history});
useEffect((prevProps) => {
    if (props.user !== previous.user) {
        props.history.push("/");
    }
}, [props.user, props.history]);

My answer was based in that answer: How to compare oldValues and newValues on React Hooks useEffect?

Diego Vinícius
  • 2,125
  • 1
  • 12
  • 23
0

In the end, I managed to solve this by adding a boolean value that would check if I need to redirect and stored that in the state - ugly but somewhat efficient.

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Logo from "../../assets/images/logo.png";
import { ReactComponent as Google } from "../../assets/icons/google.svg";
import "./Login.css";
import { connect } from "react-redux";
import { loginUser } from "../../redux/user/userAction";

const Login = (props) => {

    const [redirect, changeRedirect] = useState(false);
    const { user, history, signInWithGoogle } = props;
    useEffect(() => {
        if(redirect){
            history.push("/");
        }
    }, [user, history]);

    const handleClick = () => {
        changeRedirect(true);
        signInWithGoogle();
    }

    return (
        <div className="login-page">
            <Link to="/">
                <img src={Logo} alt="logo" className="mb-5" />
            </Link>
            <h1 className="h2">Login</h1>
            <p>Alege providerul cu care vrei să vrei să te loghezi:</p>
            <button
                className="btn btn-outline-dark d-flex align-items-center"
                onClick={() => handleClick()}
            >
                <Google className="w-50 mr-3" />
                <span className="text-nowrap">Loghează-te cu Google</span>
            </button>
        </div>
    );
};

function mapStateToProps(state) {
    return {
        user: state.user.data,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        signInWithGoogle: () => dispatch(loginUser()),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);