React Typescript component not properly dispatching a redux action

Luke Braithwaite :

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

Michael Ceber :

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'

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=34418&siteId=1