Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Typescript - How add Types to location.state when passed in a Route

I am getting an error when I pass react-router props sent to a component in a Route because I have specific state that I pass this component but the error shows up in my Route.

enter image description here

Here is the Route code:

<Route
    exact
    path='/flipltimeline'
    render={props => <FliplTimeline {...props} />

In another component I call this below

props.history.push(`/flipltimeline`, {
      Approval: singleFliplApprovals,
      InvestigationID,
      Unit: unit,
      Cost: cost
    });

Here is the code for the Component. I finally got Typescript to compile this but I had to merge LocationState & TimelineState to get this to work. But now Typescript throws the above screenshot when I send props to my FliplTimeline component. Anyone have and idea how to fix this?

history.tsx

import { createBrowserHistory } from 'history';

let baseNameProd = '';

if (process.env.NODE_ENV !== 'production') {
  console.log('Looks like we are in development mode!');
  baseNameProd = '';
} else {
  baseNameProd = '/flipl';
}

const customHistory = createBrowserHistory({
  basename: baseNameProd
});

export default customHistory;

FliplTimeline.tsx

import * as React from 'react';
import { History, LocationState } from 'history';

interface FliplTimelineLocationState {
  Approval: any;
  InvestigationID: number;
  Unit: string;
  Cost: number;
}

interface TimelineState{
  state: FliplTimelineLocationState;
}

interface Props {
  history: History;
  location: LocationState & TimelineState;
}

function FliplTimeline(props: Props) {
  return (
    <ModuleTemplate title='Status Timeline' subtitle=''>
      <FliplTimelineJumbotron className='bg-primary-darker shadow-4 line-height-serif-4'>
        <div className='grid-row'>
          <div className='grid-col-4'>
            <span
              className='font-mono-2xl text-white'
              style={{
                verticalAlign: 'middle'
              }}
            >
              FLIPL{' '}
            </span>
            <span
              className='font-mono-xl text-gold'
              style={{
                verticalAlign: 'middle'
              }}
            >
              {props.location.state.InvestigationID}
            </span>

Update: Added my history.tsx file in which I created my own history for React-Router. Also added in the import statements.

Update: Tried to change my FliplTimeline component to have this interface

import { RouteComponentProps } from 'react-router-dom'

function FliplTimeline(props: RouteComponentProps ) {

I get 2 errors. First one is this one and another that says that the shape of the props are wrong. Ideas? tserror

Update: I was able to finally get the right props declaration for my component.

import { RouteComponentProps } from 'react-router-dom';

interface FliplTimelineLocationState {
  Approval: any;
  InvestigationID: number;
  Unit: string;
  Cost: number;
}

function FliplTimeline(
  props: RouteComponentProps<{}, any, FliplTimelineLocationState | any>
)
like image 734
Paul Avatar asked Feb 21 '20 17:02

Paul


2 Answers

I was able to get it working.

import { RouteComponentProps } from 'react-router-dom';

interface FliplTimelineLocationState {
  Approval: any;
  InvestigationID: number;
  Unit: string;
  Cost: number;
}

function FliplTimeline(
  props: RouteComponentProps<{}, any, FliplTimelineLocationState | any>
)
like image 107
Paul Avatar answered Oct 18 '22 08:10

Paul


Use RouteComponentProps and feed it the appropriate types for its generic. This article covers quite a bit: How to Use React Router in Typescript.

Using React Router with TypeScript almost necessitates filling in the generics that React Router has. Otherwise there isn't enough context to determine the types of everything. Compare to my example below. I use useHistory and fill it with the type that I want to be made available. This would probably be your FliplTimelineLocationState that way the state property of history would be determinable to be of type FliplTimelineLocationState.

import React, {MouseEventHandler} from "react";
import {Route, RouteComponentProps, StaticContext, useHistory} from "react-router";
import {BrowserRouter, Link} from "react-router-dom";

interface IMyScreenRouteParams {
    foo: string;
}

// You don't have to extend, you could just use StaticContext
interface IMyStaticContext extends StaticContext {
    bar: string;
}

interface IHistory {
    fizz: string;
}

const Nav = () => {
    const history = useHistory<IHistory>();

    const clickHanlder: MouseEventHandler = () => {
        history.push("/my-screen", {
            fizz: "you said fizz"
        });
    };

    return (
        <nav>
            <ul>
                <li><Link to="/">Home</Link></li>
                <li><Link to="/my-screen">My Screen</Link></li>
                <li><button onClick={clickHanlder}>My Screen with state</button></li>
                <li><Link to="/my-screen?q=hello">My Screen with query</Link></li>
                <li><Link to="/my-screen/bob">My Screen using match</Link></li>
            </ul>
            <div>
                <button onClick={() => history.goBack()}>Back</button>
                <button onClick={() => history.push("/")}>Home</button>
                <button onClick={() => history.goForward()}>Forward</button>
            </div>
        </nav>
    );
};

const MyScreen = ({
    location,
    match,
    history,
    staticContext
}: RouteComponentProps<IMyScreenRouteParams, IMyStaticContext, IHistory>) => (
    <div>
        <section>
            <h2>Location</h2>
            <p><code>location</code> has <code>IHistory</code> props.</p>
            <pre><code>{JSON.stringify(location, null, 4)}</code></pre>
        </section>
        <section>
            <h2>Match</h2>
            <p><code>match</code> has <code>IMyScreenRouteParams</code> props.</p>
            <pre><code>{JSON.stringify(match, null, 4)}</code></pre>
        </section>
        <section>
            <h2>History</h2>
            <p><code>history</code> has <code>IHistory</code> props.</p>
            <pre><code>{JSON.stringify(history, null, 4)}</code></pre>
        </section>
        <section>
            <h2>Static Context</h2>
            <p><code>staticContext</code> has <code>IMyStaticContext</code> props or whatever static context your router has.</p>
            <p>This is for a <a href="https://reacttraining.com/react-router/web/api/StaticRouter/context-object"><code>&lt;StaticRouter/&gt;</code></a>.</p>
            <pre><code>{JSON.stringify(staticContext, null, 4)}</code></pre>
        </section>
    </div>
);

const Router = () => (
    <BrowserRouter>
        <div
            style={{
                display: "flex",
                flexDirection: "row"
            }}
        >
            <Nav />
            <main>
                <Route exact path="/" component={() => (<div>Click something in the <code>&lt;nav/&gt;</code></div>)} />
                <Route exact path="/my-screen" component={MyScreen} />
                <Route exact path="/my-screen/:id" component={MyScreen} />
            </main>
        </div>
    </BrowserRouter>
);

export default Router;
like image 41
zero298 Avatar answered Oct 18 '22 08:10

zero298