1

Have been converted a project from JavaScript to TypeScript. When the login button of the component is clicked the onSumbit even fires but the redux action is not dispatched. The vanilla JavaScript version of this works fine. The project does compile normally when built, with no errors.

The TypeScript file:

interface IErrors {
    email: string | undefined;
    password: string | undefined;
}

interface IUser {
    email: string;
    password: string;
}

interface IState {
    email: string;
    password: string;
    [key: string]: string;
}

interface IReduxProps {
    errors: IErrors;
}

interface IDispatchProps {
    loginUser: (user: IUser, history: any) => (dispatch: any) => Promise<any>;
    clearErrors: () => { type: string };
}

interface IProps extends IReduxProps {
    loginUser: (user: IUser, history: any) => (dispatch: any) => Promise<any>;
    clearErrors: () => { type: string };
}

export class Login extends React.Component<IProps, IState> {
    state = {
        email: '',
        password: '',
    };

    onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const user = {
            email: this.state.email,
            password: this.state.password,
        };
        this.props.loginUser(user, history);
    };

    onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({ [e.target.name]: e.target.value });
    };

    componentWillUnmount() {
        this.props.clearErrors();
    }

    render() {
        return (
            <div className={styles.page}>
                <div className={styles.content}>
                    <h1 className={styles.title}>Login</h1>
                    <form className={styles.form} onSubmit={this.onSubmit}>
                        <div className={styles.inputGroup}>
                            <label className={styles.label} htmlFor="email">
                                Email
                            </label>
                            <input
                                className={classNames(styles.input, {
                                    [styles.inputError]:
                                        this.props.errors && this.props.errors.email,
                                })}
                                type="text"
                                name="email"
                                id="email"
                                value={this.state.email}
                                onChange={this.onChange}
                                autoComplete="email"
                            />
                            {this.props.errors && this.props.errors.email ? (
                                <p className={styles.error}>{this.props.errors.email}</p>
                            ) : null}
                        </div>
                        <div className={styles.inputGroup}>
                            <label className={styles.label} htmlFor="password">
                                Password
                            </label>
                            <input
                                className={classNames(styles.input, {
                                    [styles.inputError]:
                                        this.props.errors && this.props.errors.password,
                                })}
                                type="password"
                                name="password"
                                id="password"
                                value={this.state.password}
                                onChange={this.onChange}
                                autoComplete="password"
                            />
                            {this.props.errors && this.props.errors.password ? (
                                <p className={styles.error}>{this.props.errors.password}</p>
                            ) : null}
                        </div>
                        <button className={styles.button} type="submit">
                            Login
                        </button>
                    </form>
                    <Link className={styles.link} to="/register">
                        I need an account
                    </Link>
                </div>
            </div>
        );
    }
}

// TODO: change state to match redux state interface
const mapStateToProps = (state: any): IReduxProps => ({
    errors: state.errors,
});

const mapDispatchToProps = (): IDispatchProps => ({
    loginUser,
    clearErrors,
});

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

The redux action:

export const loginUser = (userData, history) => dispatch => {
    return axios
        .post('/api/login', userData)
        .then(res => {
            const { token, id } = res.data;
            localStorage.setItem('token', token);
            setAuthToken(token);
            dispatch({ type: 'LOGIN', payload: { id, token } });
            history.push('/dashboard');
            dispatch({ type: 'CLEAR_ERRORS' });
        })
        .catch(err =>
            dispatch({
                type: 'GET_ERRORS',
                payload: err.response.data,
            }),
        );
};

Any help would be much appreciated

1 Answer 1

2

I think this is because you are not actually using "dispatch" anywhere.

Add bindActionCreators to your mapDispatchToProps

function mapDispatchToProps(dispatch: any, ownProps: IOwnProps): IDispatchProps {
  return bindActionCreators(
    {
       loginUser,
       clearErrors,
    },
    dispatch
  );
}

or you could use

const mapDispatchToProps = dispatch => {
  return {
    loginUser: (userData, history) => dispatch(loginUser(userData, history))
  }
}

This is all needed as you are using 'action creators'

Sign up to request clarification or add additional context in comments.

2 Comments

This was the problem. However, the bindActionCreators is required otherwise the dispatch is not bound to the action.
Yep, it would have just called a function which creates a function rather than creating a function and actually executing it...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.