mapDispatchToProps type challenge (typescript react-redux)

2.4k views Asked by At

I am not able to figure out the type of my mapDispatchToProps. Check below in the SignInComponent const mapDispatchToProps. Seems simple as the functions takes signIn() as a parameter, from whom type I have availble. However I was not able to figure that out.

This is my autAction.ts:

import firebase from 'firebase/app'
import { Dispatch } from 'react';

type AuthAction = {
  type: string,
  err?: unknown
}

export const signIn = (credentials: {email:string, password:string}) => {
  return (dispatch: Dispatch<AuthAction>) => {
    firebase.auth().signInWithEmailAndPassword(
      credentials.email,
      credentials.password
    ).then(() => {
      dispatch({ type: 'LOGIN_SUCCESS'})
    }).catch((err) => {
      dispatch({ type: 'LOGIN_ERROR', err})
    });
  }
}

And this is my SignIn component:

import React, { BaseSyntheticEvent, Component } from 'react'
import { connect } from 'react-redux';
import { signIn } from '../../store/actions/authActions';


type IConnectedDispatch = {
  signIn: typeof signIn
}

interface IMyComponentProps {
  signIn: (credentials: {email:string, password:string}) => void;
}

class SignIn extends Component<IMyComponentProps> {
  state = {
    email:'',
    password:''
  }

  handleChange = (e:BaseSyntheticEvent) => {
    this.setState({
      [e.target.id]: e.target.value
    });
  }

  handleSubmit = (e:BaseSyntheticEvent) => {
    e.preventDefault();
    //console.log(this.state);
    this.props.signIn(this.state);
  }

  render() {
    return (
      <div className="container">
        <form onSubmit={this.handleSubmit} className="white">
          <h5 className="grey-text text-darken-3">Sing In</h5>
          <div className="input-field">
            <label htmlFor="email">Email</label>
            <input type="email" id="email" onChange={this.handleChange}/>
          </div>
          <div className="input-field">
            <label htmlFor="password">Password</label>
            <input type="password" id="password" onChange={this.handleChange}/>
          </div>
          <div className="input-field">
            <button className="btn pink lighten-1 z-depth-0">Login</button>
          </div>
        </form> 
      </div>
    )
  }
}

type AuthAction = {
  type:string,
  err?: unknown
}

const mapDispatchToProps = (dispatch: any): IConnectedDispatch  => { //TYPE CHALLENGE HERE. 
  return {
    signIn: (credentials:{ email:string, password:string}) => dispatch(signIn(credentials))
  }
}

export default connect<React.FunctionComponent>(null,mapDispatchToProps)(SignIn)
2

There are 2 answers

1
Linda Paiste On BEST ANSWER

As shown in @liamgbs' answer, your mapDispatchToProps isn't really necessary, but I do want to answer your questions regarding the typescript issues.

mapDispatchToProps is a function which takes dispatch as an argument and returns a keyed object of props. So the type is essentially (dispatch: Dispatch).

The question is which Dispatch definition to use here, as multiple packages have their own typings for it. The more basic definitions of Dispatch from react and redux expect that dispatch will only be called with a plain action object. The return value from your signIn action creator is instead a function of dispatch (a "thunk"). So you need to use typings from the redux-thunk package.

{signIn: typeof signIn} is also not quite right because you are calling signIn rather than just returning it. The actual interface is your IMyComponentProps, where signIn is a void function.

import { ThunkDispatch } from "redux-thunk";

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

interface IMyComponentProps {
  signIn: (credentials: LoginCredentials) => void;
}

const mapDispatchToProps = ( dispatch: ThunkDispatch<any, never, AuthAction> ): IMyComponentProps => {
  return {
    signIn: (credentials: LoginCredentials) => dispatch(signIn(credentials))
  };
};

Annoyingly, ThunkDispatch requires you to set all three generics. That first any represents the type of your redux state, so you could replace that with an actual type if you want. The never is the extra args in your action creator which you aren't using.

1
liamgbs On

Try it like this:

import React, { BaseSyntheticEvent, Component } from 'react'
import { connect } from 'react-redux';
import { signIn } from '../../store/actions/authActions';

interface IMyComponentProps {
  signIn: typeof signIn
}

class SignIn extends Component<IMyComponentProps> {
  state = {
    email:'',
    password:''
  }

  handleChange = (e:BaseSyntheticEvent) => {
    this.setState({
      [e.target.id]: e.target.value
    });
  }

  handleSubmit = (e:BaseSyntheticEvent) => {
    e.preventDefault();
    //console.log(this.state);
    this.props.signIn(this.state);
  }

  render() {
    return (
      <div className="container">
        <form onSubmit={this.handleSubmit} className="white">
          <h5 className="grey-text text-darken-3">Sing In</h5>
          <div className="input-field">
            <label htmlFor="email">Email</label>
            <input type="email" id="email" onChange={this.handleChange}/>
          </div>
          <div className="input-field">
            <label htmlFor="password">Password</label>
            <input type="password" id="password" onChange={this.handleChange}/>
          </div>
          <div className="input-field">
            <button className="btn pink lighten-1 z-depth-0">Login</button>
          </div>
        </form> 
      </div>
    )
  }
}

export default connect(null, { signIn } )(SignIn)