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'