reactjs - Add more props to JSX.Element in typescript - Stack Overflow
State vs. Props and how to stop confusing them?
Understanding State and Props in React – Hacker Noon
Charming, concise, and even has code examples. I wish more articles were like this.
More on reddit.comprops!!!props!!! Anyone???
If you extend an object you want to construct that parent object. What you pass to that object depends on that object's needs. You could construct it without props, or maybe you need a lot more. If you don't need the props then you can skip on them.
Simple example:
class Vehicle {
constructor(props) {
this.wheels = props.wheels;
this.doors = props.doors;
this.airborne = props.airborne;
}
}
class Car extends Vehicle {
constructor(props) {
super({
...props,
airborne: false
});
}
}
const car = new Car({
doors: 4,
wheels: 4,
});
console.log(car); // {wheels: 4, doors: 4, airborne: false}
Sometimes you don't need the super call to have any parameters, and that's fine.
More on reddit.comVideos
Well the other way you can do is using Context API. Have a look at https://reactjs.org/docs/context.html#when-to-use-context for more understanding
What I would do is I would create a custom type that would contain the user id and then use this type to create react context.
Now next step would be use this context and create a provider and then render necessary components inside this context provider. You can then consume this context in any of the child component that is at any deep level nested in this custom created context and you can get the user id.
For eg.
Create custom context type and react context:
export type MyContextType = {
userId: number
}
export const MyContext = React.createContext<MyContextType>(undefined!);
Now use provider and pass the initial value in your main/root file
const context: MyContextType = {
userId: 1 (I am assuming you would get this from API response or local storage)
};
<MyContext.Provider value={context}>
...nested components
<MyComponent />
</MyContext.Provider>
and then in any of your nested component you can get this context and the value using:
class MyComponent extends React.Component<MyComponentProps, State> {
static contextType = MyContext;
context!: React.ContextType<typeof MyContext>;
render() {
console.log(this.context.userId); // You should be able to see 1 being printed in the console.
}
}
Component vs. Element
There is a difference between passing the component itself as the children prop:
<PrivateRoute>
{MyComponent}
</PrivateRoute>
and passing an instance of the component:
<PrivateRoute>
<MyComponent/>
</PrivateRoute>
The type children: JSX.Element applies to the second case and that is why you get the error:
JSX element type 'Component' does not have any construct or call signatures.
When you have already called the component through JSX like <MyComponent/> then you just get a JSX.Element which is not callable.
Typing the Component
You want to be passing a callable component. If this is react-router-dom I would recommend using the component prop instead of the children prop since they behave differently.
We want to say that our component can take the standard RouteComponentProps and also a user prop.
The PrivateRoute itself should take all of the RouteProps like to, exact, etc. But we will require a component prop with our modified type.
In order to call the component with our extra prop, we could use an inline render function render={props => <Component {...props} user={user} />}. We need to destructure it with an uppercase name in order to call it. We could also move the authentication logic into a higher-order component and use component={withUser(component)}.
Other Issues
We are going to be including this PrivateRoute alongside other Route components. So we want to render the Redirect only when we are actually on this Route. In other words, it should be a replacement of the component rather than a replacement of the Route. Otherwise traffic to other non-private routes will get inadvertently redirected.
You probably don't want to Redirect to the login page until after loading has finished. You can render a loading spinner or nothing while waiting for completion.
I'm not sure why there is so much checking and assertion on user. You should be able to do const user = data?.me; and get either a PartialUser or undefined.
Code
import React from "react";
import { Redirect, Route, RouteComponentProps, RouteProps, Switch } from "react-router-dom";
type PartialUser = {
id: number;
username: string;
email: string;
};
declare function useMeQuery(): { error: any; loading: boolean; data?: { me: PartialUser } }
type ComponentProps = RouteComponentProps & {
user: PartialUser;
}
type Props = RouteProps & {
component: React.ComponentType<ComponentProps>;
// disallow other methods of setting the render component
render?: never;
children?: never;
};
const PrivateRoute = ({ component: Component, ...rest }: Props) => {
const { data, error, loading } = useMeQuery();
const user = data?.me;
return (
<Route
{...rest}
render={(props) => {
if (user) {
return <Component {...props} user={user} />;
} else if (loading) {
return null; // don't redirect until complete
} else {
return <Redirect to="/login" />;
}
}}
/>
);
};
const MustHaveUser = ({user}: {user: PartialUser}) => {
return <div>Username is {user.username}</div>
}
export const Test = () => (
<Switch>
<Route path="/home" render={() => <div>Home</div>} />
<PrivateRoute path="/dashboard" component={MustHaveUser} />
</Switch>
)
Typescript Playground Link