reactjsreduxredux-actionsmapdispatchtoprops

I cant get Redux action to work there is some error I cant spot please advice


I cant find what I do wrong but the action saveUser is imported and the mapDispatchToProps looks like it linking the actioncorrect but still I get this error

enter image description here

(Below after this code there is the action code)

The mapDispatchToProps is linking the action saveUser and if I CTRL-click it VSCode jump to the Action like this:
So why the error "TypeError: saveUser is not a function"

enter image description here

Here in the class LinkAccounts.jsx that has the mapDispatchToProps.

/* eslint-disable max-classes-per-file */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { AuthUserContext, withAuthorization } from '../../session';
import { withFirebase } from '../../firebase';
import { SIGN_IN_METHODS } from '../../constants/signinmethods';
import * as ROLES from '../../constants/roles';
import '../../styles/link-account.scss';
import asyncSaveUser from '../../redux/userData/user.actions';

class LoginManagementBase extends Component {
    constructor() {
        super();
        this.state = {
            activeSignInMethods: [],
            anonymousSignIn: null,
            error: null,
        };
    }

    componentDidMount() {
        this.fetchSignInMethods();
    }

    fetchSignInMethods = () => {
        const { firebase, authUser } = this.props;
        const email = authUser.email === null ? 'none@guest.ac' : authUser.email;
        firebase.auth
            .fetchSignInMethodsForEmail(email)
            .then(activeSignInMethods =>
                this.setState({
                    activeSignInMethods,
                    anonymousSignIn: activeSignInMethods.length === 0,
                    error: null,
                }),
            )
            .catch(error => this.setState({ error }));
    };

    onSocialLoginLink = provider => {
        const { firebase, saveUser } = this.props;
        firebase.auth.currentUser
            .linkWithPopup(firebase[provider])
            // .linkWithRedirect(this.props.firebase[provider])
            .then(saveUser())
            .then(this.fetchSignInMethods)
            .catch(error => this.setState({ error }));
    };

    onDefaultLoginLink = password => {
        const { firebase, authUser } = this.props;
        const credential = firebase.emailAuthProvider.credential(authUser.email, password);

        firebase.auth.currentUser
            .linkAndRetrieveDataWithCredential(credential)
            .then(this.fetchSignInMethods)
            .catch(error => this.setState({ error }));
    };

    onUnlink = providerId => {
        const { firebase } = this.props;
        firebase.auth.currentUser
            .unlink(providerId)
            .then(this.fetchSignInMethods)
            .catch(error => this.setState({ error }));
    };

    render() {
        const { activeSignInMethods, anonymousSignIn, error } = this.state;
        console.log('Debug: ', anonymousSignIn, activeSignInMethods.length);
        return (
            <div className="provideToggler">
                &nbsp;&nbsp;&nbsp;
                <h1>
                    You are signed in Anonymously changes you do is only saved in this browser. If you want to access your
                    progress anywhere please sign in below!
                </h1>
                &nbsp;
                <ul>
                    {SIGN_IN_METHODS.map(signInMethod => {
                        const onlyOneLeft = activeSignInMethods.length === 1;
                        const isEnabled = activeSignInMethods.includes(signInMethod.id);

                        return (
                            <li key={signInMethod.id}>
                                {signInMethod.id === 'password' ? (
                                    <DefaultLoginToggle
                                        // accountEmail={this.props.authUser.email}
                                        onlyOneLeft={onlyOneLeft}
                                        isEnabled={isEnabled}
                                        signInMethod={signInMethod}
                                        onLink={this.onDefaultLoginLink}
                                        onUnlink={this.onUnlink}
                                    />
                                ) : (
                                    <SocialLoginToggle
                                        onlyOneLeft={onlyOneLeft}
                                        isEnabled={isEnabled}
                                        signInMethod={signInMethod}
                                        onLink={this.onSocialLoginLink}
                                        onUnlink={this.onUnlink}
                                    />
                                )}
                            </li>
                        );
                    })}
                </ul>
                {error && error.message}
            </div>
        );
    }
}

const SocialLoginToggle = ({ onlyOneLeft, isEnabled, signInMethod, onLink, onUnlink }) =>
    isEnabled ? (
        <button type="button" onClick={() => onUnlink(signInMethod.id)} disabled={onlyOneLeft}>
            Unlink <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
        </button>
    ) : (
        <button type="button" onClick={() => onLink(signInMethod.provider)}>
            Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
        </button>
    );

class DefaultLoginToggle extends Component {
    constructor() {
        super();
        this.state = { passwordOne: '', passwordTwo: '' };
    }

    onSubmit = event => {
        const { passwordOne } = this.state;
        const { onLink } = this.props;
        event.preventDefault();
        onLink(passwordOne);
        this.setState({ passwordOne: '', passwordTwo: '' });
    };

    onChange = event => {
        this.setState({ [event.target.name]: event.target.value });
    };

    render() {
        const { signInMethod } = this.props;
        const { passwordOne, passwordTwo } = this.state;
        const isInvalid = passwordOne !== passwordTwo || passwordOne === '';
        return (
            <form onSubmit={this.onSubmit}>
                Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
                <input
                    name="passwordOne"
                    value={passwordOne}
                    onChange={this.onChange}
                    type="password"
                    placeholder="Password for email sign in"
                />
                <input
                    name="passwordTwo"
                    value={passwordTwo}
                    onChange={this.onChange}
                    type="password"
                    placeholder="Confirm New Password"
                />
                <button disabled={isInvalid} type="submit">
                    Save password for email sign in
                </button>
            </form>
        );
    }
}

const LinkAccounts = () => (
    <AuthUserContext.Consumer>
        {authUser => (
            <div>
                <LoginManagement authUser={authUser} />
            </div>
        )}
    </AuthUserContext.Consumer>
);

const mapDispatchToProps = dispatch => ({
    saveUser: () => dispatch(asyncSaveUser()),
});

const LoginManagement = withFirebase(LoginManagementBase);
const condition = authUser => authUser && authUser.roles.includes(ROLES.ANON);

const enhance = compose(withAuthorization(condition), connect(null, mapDispatchToProps));

export default enhance(LinkAccounts);

This is the Action in file user.actions.js:

import { userActionTypes } from './user.types';
import { withFirebase } from '../../firebase';
import * as ROLES from '../../constants/roles';

const saveUserStart = () => ({
    type: userActionTypes.SAVE_USER_START,
});

const saveUserSuccess = user => ({
    type: userActionTypes.SAVE_USER_SUCCESS,
    payload: user,
});

const saveUserFailure = errMsg => ({
    type: userActionTypes.SAVE_USER_FAILURE,
    payload: errMsg,
});

const asyncSaveUser = ({ firestore }) => {
    return async dispatch => {
        const userRef = firestore.userDoc(firestore.auth.currentUser.uid);
        dispatch(saveUserStart());
        firestore.db
            .runTransaction(transaction => {
                // This code may get re-run multiple times if there are conflicts.
                return transaction.get(userRef).then(doc => {
                    if (!doc.exists) {
                        return Promise.reject('Transaction failed: User dont exist!');
                    }
                    const newRoles = doc.data().roles;
                    // new roll
                    newRoles.push(ROLES.USER);
                    // remove roll
                    newRoles.splice(newRoles.indexOf('ANONYMOUS'), 1);
                    // save it back
                    transaction.update(userRef, { roles: newRoles });
                    return newRoles;
                });
            })
            .then(newRoles => {
                dispatch(saveUserSuccess());
                console.log(`Transaction successfully committed role(s): ${newRoles}`);
            })
            .catch(error => {
                dispatch(saveUserFailure(error));
                console.log(error);
            });
    };
};

export default withFirebase(asyncSaveUser);

Solution

  • saveUser is not passed as a prop in LoginManagementBase component.

    It is only passed as prop via enhance for LinkAccounts.

    I think you want to compose for LoginManagement instead.

    const enhance = compose(
      withFirebase,
      connect(null, mapDispatchToProps)
    );
    
    const LoginManagement = enhance(LoginManagementBase);