Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React TypeScript HoC - passing Component as the prop

Following this tutorial: https://reacttraining.com/react-router/web/example/auth-workflow.

Trying to reproduce the code:

const PrivateRoute = ({ component: Component, ...rest }) => (   <Route     {...rest}     render={props =>       fakeAuth.isAuthenticated ? (         <Component {...props} />       ) : (         <Redirect           to={{             pathname: "/login",             state: { from: props.location }           }}         />       )     }   /> ); 

In TypeScript:

import * as React from 'react'; import { Route, RouterProps } from 'react-router';  interface Props extends RouterProps {   component: React.Component; }  const PrivateRoute = ({ component: Component, ...rest }: Props) => {   return (     <Route       {...rest}       render={(props) => <Component {...props} />}     />   ); };  export default PrivateRoute; 

But it would always fail. Tried different variations. The one I've posted the most recent one. Getting:

enter image description here

It seems to me that I have to pass Generic for the Component type, but I don't know how.

EDIT:

The closest solution so far:

interface Props extends RouteProps {   component: () => any; }  const PrivateRoute = ({ component: Component, ...rest }: Props) => {   return (     <Route       {...rest}       render={(props) => <Component {...props} />}     />   ); }; 

And then:

<PrivateRoute component={Foo} path="/foo" /> 
like image 701
0leg Avatar asked Mar 14 '18 09:03

0leg


People also ask

Can you pass a React component as prop?

You can pass a component as props in React by using the built-in children prop. All elements you pass between the opening and closing tags of a component get assigned to the children prop. Copied!

Can we pass props in Hoc?

Approach 1 - React. We are passing a simple div to the HOC. Div, span, p, h1 or any other usual DOM elements are only allowed to accept their usual properties like id, className, role, etc, which makes sense. They can't be given custom props unless we use data-attributes.

How do I pass JSX as a prop?

You want to use JSX inside your props You can simply use {} to cause JSX to parse the parameter. The only limitation is the same as for every JSX element: It must return only one root element.


2 Answers

You want to pass a component constructor, not a component instance:

import * as React from 'react'; import { Route, RouteProps } from 'react-router';  interface Props extends RouteProps {     component: React.ComponentType; }  const PrivateRoute = ({ component: Component, ...rest }: Props) => {     return (         <Route             {...rest}             render={(props) => <Component {...props} />}         />     ); };  export default PrivateRoute;  class Foo extends React.Component {  } let r = <PrivateRoute component={Foo} path="/foo" /> 

Edit

A more complete solution should be generic and use RouteProps instead RouterProps:

import * as React from 'react'; import { Route, RouteProps } from 'react-router';  type Props<P> =  RouteProps & P & {     component: React.ComponentType<P>; }  const PrivateRoute = function <P>(p: Props<P>) {     // We can't use destructuring syntax, because : "Rest types may only be created from object types", so we do it manually.     let rest = omit(p, "component");     let Component = p.component;     return (         <Route             {...rest}             render={(props: P) => <p.component {...props} />}         />     ); };  // Helpers type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];   type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;  function omit<T, TKey extends keyof T>(value:T, ... toRemove: TKey[]): Omit<T, TKey>{     var result = Object.assign({}, value);     for(let key of toRemove){         delete result[key];     }     return result; }   export default PrivateRoute;  class Foo extends React.Component<{ prop: number }>{  } let r = <PrivateRoute component={Foo} path="/foo" prop={10} /> 
like image 166
Titian Cernicova-Dragomir Avatar answered Sep 20 '22 01:09

Titian Cernicova-Dragomir


After a few hours and some investigation, here is the solution that fits my requirements:

import * as React from 'react'; import { Route, RouteComponentProps, RouteProps } from 'react-router';  const PrivateRoute: React.SFC<RouteProps> =   ({ component: Component, ...rest }) => {     if (!Component) {       return null;     }     return (       <Route         {...rest}         render={(props: RouteComponentProps<{}>) => <Component {...props} />}       />     );   };  export default PrivateRoute; 

  • No any;
  • No extra complexity;
  • Composition pattern retained;
like image 38
0leg Avatar answered Sep 23 '22 01:09

0leg