2
votes

I want to save a user to Firebase's Realtime Database upon user creation in a sign-up form. If I return the Firebase function (value), for saving users, in a .then handler instead of just calling it (without return statement), I get an error message from React, saying "Can't perform a react state update on an unmounted component".

My code for the submit handler of the sign-up form looks something like the following:

 const SignUpFormBase = (props) => {
   const [credentials, setCredentials] = useState(INITIAL_STATE);
   const [error, setError] = useState(null);

   [some other code]

   const handleSubmit = (e) => {
        firebase
            .doCreateUserWithEmailAndPassword(email, passwordOne)
            .then(authUser => {

                // create a user in the Firebase realtime database
                return firebase.database().ref(`users/${authUser.user.uid}`)
                       .set({ username, email });

            })
            .then(() => {
                setCredentials(INITIAL_STATE);
                props.history.push(ROUTES.DASHBOARD);
            })
            .catch(error => {
                setError(error);
            });

        e.preventDefault();
    };

  return (
    [some jsx]
  );
 };

const SignUpForm = withRouter(SignUpFormBase);

export default SignUpForm;

The code actually works, whether you include or leave off the return statement. However, if you don't use a return statement, the warning won't show.

I just don't understand why I get the above-mentioned warning from firebase since I (seemingly) don't perform any state updates on the component after it has been unmounted.

Update:

The component actually unmounts before the setCredentials hook has the chance to update the state. This is not because of the push to history in the code above, but a Public Route I've implemented to show users only pages they are allowed to see. It uses the new useContext hook which triggers a re-render when the context value changes (the value is derived from subscribing to the firebase onAuthStateChanged method). I solved the issue by putting the setCredentials call into a useEffect hook:

useEffect(() => {
    // set credentials to INITIAL_STATE before ComponentDiDUnmount Lifecycle Event
    return () => {
        setCredentials(INITIAL_STATE);
    };
}, []);

However, I still don't get why a missing return statement (in the previous setup) lead to a vanishing react warning.

2
Can you share setCredentials code ? - Utsav Patel
It means you are destroying the component and some part of code trying to update the state. - Laxmikant Dange
@UtsavPatel: updated code block. - andreW
Thank you @andreW . This solution was the solution I was looking for! - Phil Tran

2 Answers

2
votes

Telling from given code, I'm not sure what part lead to the warning. However, I'd like to provide some advices to help pin point it (Hard to write it clear in comment, so I just post as an answer).

firebasePromise
    .then(() => {
        // [1] async code
        setCredentials(INITIAL_STATE);

        // [2] sync code
        // UNMOUNT happens after route change
        props.history.push(ROUTES.DASHBOARD); 
    })
    .catch(error => {
        // [3] async code
        setError(error);
    });

I suspect the problem comes from the execution order of sync/async code. Two things you can try out to gain more info.

  1. Check if [3] is invoked, use console.log maybe?
  2. Wrap [2] in a setTimeout(() => {}) to make it async too.

My bet is on [3]. somehow some error is thrown after [2] is invoked.

0
votes

Your problem is here:

setCredentials(INITIAL_STATE);
props.history.push(ROUTES.DASHBOARD);

most likely setCredentials is async function and when it is trying to change state you already have different route because of props.history.push(ROUTES.DASHBOARD) as the result you don't have component and you can't update the state

try:

setCredentials(INITIAL_STATE).then(
   props.history.push(ROUTES.DASHBOARD);
)