1

I´m not sure how to use useEffect(), I keep getting

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in Login

here is my component:

import React, { Component, useState, useEffect } from 'react';
import fire from '../fire';

import Sign from './sign';
import Dashboard from './dashboard';
import Map from './journey/map';

const Login = () => {


const [user, setUser ] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const [hasAccount, setHasAccount] = useState(false);


const clearInputs = () => {
    setEmail('');
    setPassword('');
}

const clearErrors = () => {
    setEmailError('');
    setPasswordError('');
}

const handleLogin = () => {
    clearErrors();
    fire
        .auth()
        .signInWithEmailAndPassword(email, password)
        .catch(err => {
            switch(err.code){
                case 'auth/invalid-email':
                case 'auth/user-disabled':
                case 'auth/user-not-found':
                    setEmailError(err.message);
                    break;
                case 'auth/wrong-password':
                    setPasswordError(err.message);
                    break;
            }
        });
}

const handleSignUp = () => {
    clearErrors();
    fire
    .auth()
    .createUserWithEmailAndPassword(email, password)
    .catch(err => {
        switch(err.code){
            case 'auth/email-already-in-use':
            case 'auth/invalid-email':
                setEmailError(err.message);
                break;
            case 'auth/weak-password':
                setPasswordError(err.message);
                break;
        }
    });
    return 
}

const handleLogOut = () => {
    fire.auth().signOut();
};


const authListener = (user_id) => {
    user_id = '';
    fire.auth().onAuthStateChanged(user => {
        if(user) {
            clearInputs();
            setUser(user);
            user_id = user.email;
        } else {
            setUser('');
        }
    });
}

useEffect(() => {
    authListener();
    
}, []);

    return (
        
        <div>
            
            {user ? (
                <div>
                <Dashboard 
                handleLogOut={handleLogOut}
                user_id={user.email}
                />
                <Map 
                user_id={user.email}
                />
                </div>
            ) : (
                <Sign 
                email={email}
                setEmail={setEmail}
                password={password}
                setPassword={setPassword}
                handleLogin={handleLogin}
                handleSignUp={handleSignUp}
                hasAccount={hasAccount}
                setHasAccount={setHasAccount}
                emailError={emailError}
                passwordError={passwordError}
                />  
                
            )}
            
            
        

        </div>
    );

}

export default Login;

I am using firebase authentication.

Notes the memory leak happens only when I enter into the child component and then go back and log out of the app. here is the child component.

import React, { Component } from 'react';
import axios from 'axios';
import { Link } from "react-router-dom";

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCaretSquareLeft } from '@fortawesome/free-solid-svg-icons'

import MapItem from '../mapItems';
import Card from './card';

class Cards extends Component {
constructor(props) {
    super(props);

    this.state = {
        journey_name: [],
        cards: [],
        data: [],
        hero: [],
        loading: true
    }

    this.cards = this.cards.bind(this);        
}


fetchCards = () => {

    axios.get('myapi').then(response => {
        console.log('fetchCards',response);
        this.setState({
            journey_name: response.data.journey_name,
            cards: response.data.cards,
        })
    }).catch(error => {
        console.log(`fetchCards error : ${error}`);
    }) 

}

fetchData = () => {
    axios.get('my api').then(response => {
        console.log('fetchdata',response);
        this.setState({
            data: response.data,
            hero: response.data.hero
        })
    }).catch(error => {
        console.log(`fetchData error : ${error}`);
    })
}

componentDidMount() {
    this.fetchCards();
    this.fetchData();

}

cards = () => {
    return (
        this.state.cards.map(card => {
            return (
                <Card
                    key={card.card_id}
                    card_name={card.card_name}
                    explanation={card.explanation}
                    image_url={card.image}
                    points={card.points}
                    reference={card.reference}
                />
            )
        })
    )
}


render() {

    return (
        <div className='cards-page'>
            <div className='cards-page__navbar'>
                <Link to='/'>
                    <FontAwesomeIcon icon={faCaretSquareLeft} />
                </Link>
                <MapItem
                    name={this.state.data.name}
                    level={this.state.hero.level}
                    points={this.state.hero.points}
                    class='cards-user'
                />
            </div>

            <div className='cards-rail'>
                {this.cards()}
            </div>

            <div className='footer'> by Johnattan M Angeles </div>
        </div>
    );
}
}

export default Cards;
  • Does this answer your question? [React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing](https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret) – Michael Freidgeim Feb 21 '23 at 19:00

1 Answers1

0

Though you should probably unsubscribe from the firebase onAuthStateChanged event, I think your issue may be with the axios fetch requests. If they later resolve after you've already navigated away from the child component then they are updating state of an unmounted component. You should cancel any pending requests when the component unmounts.

class Cards extends Component {
  constructor(props) {
    super(props);

    ...

    // Create cancel token & source
    const CancelToken = axio.CancelToken;
    this.source = CancelToken.source();
  }


  fetchCards = () => {
    axios.get(
      'myapi',
      { cancelToken: this.source.token }, // <-- pass cancel token W/ request
    ).then(response => {
      console.log('fetchCards',response);
      this.setState({
        journey_name: response.data.journey_name,
        cards: response.data.cards,
      })
    }).catch(error => {
      console.log(`fetchCards error : ${error}`);
    }) 

  }

  fetchData = () => {
    axios.get(
      'my api',
      { cancelToken: source.token }, // <-- pass cancel token W/ request
    ).then(response => {
      console.log('fetchdata',response);
      this.setState({
        data: response.data,
        hero: response.data.hero
      })
    }).catch(error => {
      console.log(`fetchData error : ${error}`);
    })
  }

  componentDidMount() {
    this.fetchCards();
    this.fetchData();
  }

  componentWillUnmount() {
    this.source.cancel('Operation cancelled by user'); // <-- cancel on unmount
  }

  ...
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181