Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type or interface for location.state in react-router

In one component, there is a <Link> (from react-router-dom) passing an object in the state property.

Another component called ReceiverComponent is receiving that object correctly. However, the code belows complains with the message:

Type 'PoorMansUnknown' is not assignable to type 'locationStateProps'. Type 'undefined' is not assignable to type 'locationStateProps'.ts(2322)

type locationStateProps = {
  name: string;
}

function ReceiverComponent() {
  const location = useLocation();
  const myState: locationStateProps = location.state;
  return (
    <div>
      {myState.name}
    </div>
  );
}

I need to provide a type for the state some way without this error. How to proceed?

like image 982
fjplaurr Avatar asked Mar 08 '20 10:03

fjplaurr


3 Answers

You can also build your own state interface and cast it to it:

interface CustomState {
  ...
}

const location = useLocation()
const state = location.state as CustomState
like image 73
lewislbr Avatar answered Sep 21 '22 20:09

lewislbr


There is a thread open on Github about this issue.

I implemented this solution mentioned on the thread and it worked.

interface Props extends RouteComponentProps<
  { myParamProp?: string }, // props.match.params.myParamProp
  any, // history
  { myStateProp?: string }, // props.location.state.myStateProp
> {
  myNormalProp: boolean;
}
like image 44
Alfrex92 Avatar answered Sep 19 '22 20:09

Alfrex92


For others who may end up here… I'm using React Router v6, which, as far as I can tell, no longer exports RouteComponentProps (I installed @types/react-router and @types/react-router-dom — I see the type definition in the DefinitelyTyped files, but there is no export from react-router-dom).

So, the way Location is typed, I found myself unable to extend it in a way that would placate Typescript. However...

I wouldn't recommend as casting, because it reduces the efficacy of TypeScript. Good news! You can use is instead of as to prove to TypeScript that a given element is indeed what you say it is. E.g.:

type LocationStateProps = {
  name: string;
}

function ReceiverComponent() {
  const location = useLocation();
  const myState = location.state;

  // note that you are defining the return type of this function using `is`
  const hasNameProps = (state: object | null | LocationStateProps): state is LocationStateProps => {
    // you use `as` here, but only so typescript doesn't yell at you while you access the property
    // specifically to test if it is there
    return (state as LocationStateProps).name !== undefined;
  }


  return (
    <div>
      {/**
       * Now you can check to see if the prop exists. If it does, us it confidently, and TypeScript won't
       * complain! But you're able to provide a fallback in case the value is, for some reason, not there.
       * If you as-cast, you lose that opportunity. It's a little verbose, but it preserves the benefit of
       * TypeScript... Type safety!
       */}
      {hasNameProps(myState) ? myState.name : "No Name"}
    </div>
  );
}
like image 44
haiba Avatar answered Sep 18 '22 20:09

haiba