okta / okta-react

Okta OIDC SDK for React
https://github.com/okta/okta-react
Other
118 stars 79 forks source link

Okta React v5 + TypeScript import issue #99

Open mraible opened 3 years ago

mraible commented 3 years ago

I'm trying to upgrade OktaDev Schematics to use Okta React v5. Here's the code I'm trying to make work:

import React, { Component } from 'react';
import { withOktaAuth } from '@okta/okta-react';
import './App.css';
import logo from './logo.svg';
import {OktaAuth} from "@okta/okta-auth-js";

interface HomeProps {
  oktaAuth: OktaAuth;
  authState: any;
}

interface HomeState {
}

export default withOktaAuth(class Home extends Component<HomeProps, HomeState> {
  constructor(props: HomeProps) {
    super(props);
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
  }
  async login() {
    await this.props.oktaAuth.signInWithRedirect();
  }
  async logout() {
    await this.props.oktaAuth.signOut();
  }
...
}

It throws the following error when I try to run it:

TypeScript error in /Users/mraible/dev/okta/schematics/apps/react-app-ts/src/Home.tsx(15,35):
Argument of type 'typeof Home' is not assignable to parameter of type 'ComponentType<IOktaContext>'.
  Type 'typeof Home' is not assignable to type 'ComponentClass<IOktaContext, any>'.
    Construct signature return types 'Home' and 'Component<IOktaContext, any, any>' are incompatible.
      The types of 'props' are incompatible between these types.
        Type 'Readonly<HomeProps> & Readonly<{ children?: ReactNode; }>' is not assignable to type 'Readonly<IOktaContext> & Readonly<{ children?: ReactNode; }>'.
          Property '_onAuthRequired' is missing in type 'Readonly<HomeProps> & Readonly<{ children?: ReactNode; }>' but required in type 'Readonly<IOktaContext>'.  TS2345

    13 | }
    14 | 
  > 15 | export default withOktaAuth(class Home extends Component<HomeProps, HomeState> {
       |                                   ^
    16 |   constructor(props: HomeProps) {
    17 |     super(props);
    18 |     this.login = this.login.bind(this);

I was able to fix it by changing HomeProps to extend IOktaContext:

import {IOktaContext} from "@okta/okta-react/bundles/types/OktaContext";

interface HomeProps extends IOktaContext {
  oktaAuth: OktaAuth;
  authState: any;
}

Since the import looks kinda funny, I tried using OktaContext instead:

import { OktaContext, withOktaAuth } from '@okta/okta-react';

This results in:

'OktaContext' refers to a value, but is being used as a type here. Did you mean 'typeof OktaContext'?  TS2749

     5 | import {OktaAuth} from "@okta/okta-auth-js";
     6 | 
  >  7 | interface HomeProps extends OktaContext {
       |                             ^
     8 |   oktaAuth: OktaAuth;
     9 |   authState: any;
    10 | }

Adding onAuthRequired also works, but the import is similarly funny looking.

import {OnAuthRequiredFunction} from "@okta/okta-react/bundles/types/OktaContext";

interface HomeProps {
  oktaAuth: OktaAuth;
  authState: AuthState;
  _onAuthRequired: OnAuthRequiredFunction;
}
mraible commented 3 years ago

This seems to work with no funky imports.

interface HomeProps {
  oktaAuth: OktaAuth;
  authState: AuthState;
  _onAuthRequired: any;
}

Hopefully this issue inspires some component-based TypeScript examples in the README. :)

aarongranick-okta commented 3 years ago

can you not do:

import {OnAuthRequiredFunction} from "@okta/okta-react";

?

mraible commented 3 years ago

No. This code:

import React, { Component } from 'react';
import { AuthState, OktaAuth } from '@okta/okta-auth-js';
import { withOktaAuth, OnAuthRequiredFunction } from '@okta/okta-react';
import './App.css';
import logo from './logo.svg';

interface HomeProps {
  oktaAuth: OktaAuth;
  authState: AuthState;
  _onAuthRequired: OnAuthRequiredFunction;
}

Results in:

Module '"@okta/okta-react"' has no exported member 'OnAuthRequiredFunction'.  TS2305
mraible commented 3 years ago

I have another TypeScript issue when trying to upgrade my App.tsx:

Argument of type 'typeof App' is not assignable to parameter of type 'ComponentType<RouteComponentProps<any, StaticContext, unknown>>'.
  Type 'typeof App' is not assignable to type 'ComponentClass<RouteComponentProps<any, StaticContext, unknown>, any>'.
    Construct signature return types 'App' and 'Component<RouteComponentProps<any, StaticContext, unknown>, any, any>' are incompatible.
      The types of 'props' are incompatible between these types.
        Type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>' is not assignable to type 'Readonly<RouteComponentProps<any, StaticContext, unknown>> & Readonly<{ children?: ReactNode; }>'.
          Type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>' is missing the following properties from type 'Readonly<RouteComponentProps<any, StaticContext, unknown>>': history, location, match  TS2345

    32 | }
    33 | 
  > 34 | const AppWithRouterAccess = withRouter(App);
       |                                        ^
    35 | 
    36 | class RouterApp extends Component {
    37 |   render() {

Here's my code:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, withRouter } from 'react-router-dom';
import { OktaAuth, OktaAuthOptions, toRelativeUrl } from '@okta/okta-auth-js';
import { LoginCallback, Security } from '@okta/okta-react';
import Home from './Home';

const config: OktaAuthOptions = {
  issuer: 'https://dev-foo.okta.com/oauth2/default',
  clientId: 'bar',
  redirectUri: window.location.origin + '/callback'
};
const oktaAuth = new OktaAuth(config);

class App extends Component {
  restoreOriginalUri: any;

  constructor(props: any) {
    super(props);
    this.restoreOriginalUri = async (_oktaAuth: OktaAuth, originalUri: string) => {
      props.history.replace(toRelativeUrl(originalUri, window.location.origin));
    };
  }

  render() {
    return (
      <Security oktaAuth={oktaAuth} restoreOriginalUri={this.restoreOriginalUri}>
        <Route path="/" exact={true} component={Home}/>
        <Route path="/callback" component={LoginCallback}/>
      </Security>
    );
  }
}

const AppWithRouterAccess = withRouter(App);

class RouterApp extends Component {
  render() {
    return (<Router><AppWithRouterAccess/></Router>);
  }
}

export default RouterApp;

If I add // @ts-ignore, it works.

// @ts-ignore
const AppWithRouterAccess = withRouter(App);

FWIW, there's the PR to upgrade OktaDev Schematics.