14

I'd like to take advantage of the static and strong typing in TypeScript, but only for the state since I don't intend to take in any props.

When I attempt to pass the interface as such, I end up with an error:

import * as React from 'react';
import {Link} from 'react-router-dom';
import Constants from '../Constants';

interface ILoginState {
   email: string;
   password: string;
   remember: boolean;
   error: string;
   isLoading: boolean;
}

class LoginView extends React.Component<{}, ILoginState> {

   constructor(props) {
      super(props);

      this.state = {
         email: '',
         password: '',
         remember: false,
         error: '',
         isLoading: false
      };
   }

 render() {
      return (<div>Login goes here</div>
      );
   }
}

export default LoginView;

I end up with a compile error:

ERROR in [at-loader] ./src/scripts/pages/LoginView.tsx:41:21 
    TS2345: Argument of type '{ [x: number]: any; }' is not assignable to parameter of type 'ILoginState | ((prevState: Readonly<ILoginState>, props: {}) => ILoginState | Pick<ILoginState, "...'.
  Type '{ [x: number]: any; }' is not assignable to type 'Pick<ILoginState, "email" | "password" | "remember" | "error" | "isLoading">'.
    Property 'email' is missing in type '{ [x: number]: any; }'.

I've also tried using 'any' in place of the empty brackets but that doesn't work either.

Here's the line 41 (this.SetState...) that the trace is referring to:

   handleChange = event => {
      const target = event.target;
      const value = target.type === 'checkbox' ? target.checked : target.value;
      this.setState({
         [target.name]: value
      });
   }

Here are all examples where that's used:

<input name="email" type="email" value={this.state.email} onChange={this.handleChange} />
<input name="password" type="password" value={this.state.password} onChange={this.handleChange} />
<input name="remember" type="checkbox" checked={this.state.remember} onChange={this.handleChange} />
Seth Killian
  • 908
  • 9
  • 20
  • The error indicates the problem is with the type of the state, not the props. Which line of your snippet is line 41? (can you just add a comment on that line so we know where the error is)? Moreover, from the error, it looks like the problem is with a `setState` call. Do you have a `setState` call in that component that you left out of the example? If so, can you include it? – CRice Jun 05 '18 at 00:06
  • @CRice Added the requested snippet – Seth Killian Jun 05 '18 at 00:10
  • 1
    Looks like `event.target.name` is inferred to have type `number` (not sure why), and `target.value` type `any`. Since the name inferred as `number`, I don't think you can cast it to be `keyof ILoginState` (which would have been the best option). This may be one of those cases where the easiest thing is just to cast the state object to any. EG: `this.setState({[target.name]: value} as any)`, since I'm not sure where that `number` inference is coming from, or what you'd have to change to correct it. Hopefully someone who knows more about react can give a better answer. – CRice Jun 05 '18 at 01:03

1 Answers1

12

Option 1

class LoginView extends React.Component<{}, ILoginState> {
   constructor({}) {
   }
}

Option 2

class LoginView extends React.Component<null, ILoginState> {
   constructor(null) {
   }
}

Since props default value is an empty object, I personally prefer option 1.

Edit: it appears latest typescript versions do not support option 1. I'll suggest one of the following methods (tested on TS 3.7):

class LoginView extends React.Component<{}, ILoginState> {
   constructor() {
       super({});
   }
}

// OR
class LoginView extends React.Component<{}, ILoginState> {
   constructor(props = {}) {
       super(props);
   }
}

Thanks to @Andy who brought this update to my attention.

yuval.bl
  • 4,890
  • 3
  • 17
  • 31
  • 4
    I find option 1 gives me an error when I try to instantiate the component in jsx like ``. I get error TS2322: `Type '{}' is not assignable to type 'never'` – Andy Apr 22 '20 at 09:27
  • Nether of these work in 2022 for the defalt typescript/recommended rules `Don't use \`{}\` as a type. \`{}\` actually means "any non-nullish value". - If you want a type meaning "any object", you probably want \`Record\` instead. - If you want a type meaning "any value", you probably want \`unknown\` instead. - If you want a type meaning "empty object", you probably want \`Record\` instead.eslint@typescript-eslint/ban-types` – gman Oct 27 '22 at 18:45
  • @gman This actually works perfectly on TS 4.8.4 (using create-react-app). If you've added an extra ESlint rule, you can change it accordingly. see https://stackoverflow.com/a/66773898/7126139 – yuval.bl Nov 06 '22 at 11:24