This is more of a Typescript and React question but for full disclosure, I'll mention that I ended up here while trying to create a custom header using react-navigation.
I have a simple ImageButton (functional) component, which accepts the following props:
export interface ImageButtonProps extends TouchableOpacityProps {
transparent?: boolean;
}
My custom header looks (roughly) like this:
export default function Header(props: StackHeaderProps) {
const options = props.scene.descriptor.options;
const HeaderRight = options.headerRight as (() => ReactElement<ImageButtonProps>);
return (<HeaderRight transparent={true}/>); // <-- Typescript complains here
}
It's safe to assume that options.headerRight will always return an ImageButton, which in fact renders just fine. The only issue is that the property transparent is always undefined and also Typescript throws the following error:
TS2322: Type '{ transparent: boolean; }' is not assignable to type 'IntrinsicAttributes'. Property 'transparent' does not exist on type 'IntrinsicAttributes'.
So my question is, how can I properly cast a ReactElement to my custom component, ImageButton and its props?
Your error is because the type you casted it to doesn't take any arguments or props.
This means, a function which takes no arguments, returns React.Element:
() => ReactElement<ImageButtonProps>
You want it to take the props, so you need to type in the props:
(props: ImageButtonProps) => ReactElement<any>
Or
React.ComponentType<ImageButtonProps>
Also, TypeScript doesn't support typechecking ReactElement, so there's no need for doing ReactElement<ImageButtonProps>.
Also see https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
Related
How can I type a prop that accepts ComponentType or a string?
Suppose the following code.
interface MyComponentProps {
Component: React.ComponentType
}
const MyComponent: React.FC<PropsWithChildren<MyComponentProps>> = ({Component}) => {
return <Component>{children}</Component>
}
<MyComponent
Component="span"
/>
Trying this in TS gives the following error.
Type 'string' is not assignable to type 'ComponentType<{}> | undefined'.
How can I dynamically render a native html tag using JSX?
In terms of react and javascript, your code is correct. The issue here is the types. The string "span" is not a ComponentType. The type you want for Component is React.ElementType. If you check out the definition of ElementType it includes all of the intrinsic jsx elements or a component type.
You can come to this conclusion by yourself by rendering a <span></span> and hovering over or clicking through to the span keyword. You'll see the type is JSX.IntrinsicElements.span. If you click through you can see that IntrinsicElements includes all the html elements. I did a search to see where that type was being used which led me to the React.ElementType
Updated code:
interface MyComponentProps {
Component: React.ElementType
}
const MyComponent: React.FC<PropsWithChildren<MyComponentProps>> = ({Component}) => {
return <Component>{children}</Component>
}
<MyComponent
Component="span"
/>
Side note: React.FC might not be the best approach anymore. Instead it seems to be preferable to just assign a type to the props directly:
const MyComponent = ({Component}: PropsWithChildren<MyComponentProps>) => {
return <Component>{children}</Component>
}
More on that here if you're interested: https://github.com/facebook/create-react-app/pull/8177
The functional component:
const NewRestRequestCoreForm = props => {
const intl = useIntl();
....
return (
<Container>
....
</Container>
);
}
export default React.memo(NewRestRequestCoreForm);
WebStorm shows me this error/warning:
Argument type function(any): JSX.Element is not assignable to parameter type SFC<object> ...
Type function(any): JSX.Element is not assignable to type FunctionComponent<object>
Type JSX.Element is not assignable to type ReactElement<any, any> | null
I simply want to cache this component so when the parent changes but the props passed to NewRestRequestCoreForm don't, NewRestRequestCoreForm does not get re-rendered.
The component is used like this:
<NewRestRequestCoreForm a={a}/>
What I'm doing wrong and how can I fix this?
EDIT:
I just used a simple Component with and without applying memo and it works even with the warning!
I have a custom Type
export class MyClass {
name: string;
img: string;
constructor(name: string) {
this.name = name;
this.img = `/images/${name}.jpg`;
}
}
I have a stateless functional component which takes that type as a parameter
export default (issue: Issue) => {
...
}
And when I try to create that stateless functional component
<MagazineCard issue={issues[0] as Issue} />
I get the error
Type '{ issue: any; }' is not assignable to type 'IntrinsicAttributes & Issue'.
There is a solution below, but I felt I should leave this material up for other's who might see this question for alternative solutions and deeper understanding
Object destructuring when initializing the JSX, ex:
<MagazineCard {...issues[0]} />
Create an interface for the arguments of my stateless functional component.
Note: types don't need to be specified
Proptypes
MagazineCard.propTypes = {
issue: Issue
}
This is how it's done
export default ({issue} : {issue : Issue}) => (
//...Your JSX component
)
I was confused because it seemed over complicated to me, so I made a post on reddit asking why this is the syntax for doing it
I'm using TypeScript for a React project. Based on what I can see in the type definitions componentWillReceiveProps' first argument should be of the same generic type passed to the Component class. I thought that Component would already implement ComponentLifecycle, but I have also tried using implements ComponentLifecycle directly, and I'm still not getting the type safety I would expect.
interface Props {
user: { login: string };
}
class Comp extends React.Component<Props> {
componentWillReceiveProps({ user }) {
// I think this should be two errors:
// `Property `ogin` does not exist on type
// Type `string` is not assignable to type 'void';
return user.ogin;
}
}
However I do not get either error and it seems like the Props that get passed into componentWillReceiveProps only maintain their types at one level. In order to get this to work, I can write it like so:
componentWillReceiveProps({ user }: Props): void {
Is there any way to get TypeScript to properly type the generic argument for an implemented interface? Is this a bug in the type definitions?
No, this is not a bug. Definition of React.Component in typings is:
class Component<P, S> {
constructor(props?: P, context?: any);
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S)) | (Pick<S, K> | S),
callback?: () => any
): void;
forceUpdate(callBack?: () => any): void;
render(): JSX.Element | null | false;
props: Readonly<{ children?: ReactNode }> & Readonly<P>;
state: Readonly<S>;
context: any;
refs: {
[key: string]: ReactInstance
};
}
There is nothing about lifecycle methods and their types. From the point of view of typescript you are simply extending React.Component with new methods and it tries to infer the type of argument from user code in function. When you add types explicitly Typescript understands your intentions and helps you. I now only one way to type this precisely - define Component class as abstract and mark all lifecycle methods as abstract. But in this case you will have to override these methods in child class definition, but it is not you want to do most part of time with React. If you just define lifecycle methods on regular class definition like React.Component you still can override this definition with other parameters, so typescript wont force you to keep the same shape of function and prevent you from using unknown params. I think it only be fixed by adding sealed function attribute to typescript which will prohibit overriding of function type for children.
Given the following components, I get an error about a missing isUpdating prop. How do I fix that?
Error:(87, 27) TS2322:Type '{ name: "Fred"; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes> & Reado...'.
Type '{ name: "Fred"; }' is not assignable to type 'Readonly'.
Property 'isUpdating' is missing in type '{ name: "Fred"; }'.
interface ContaineeProps {
name: string;
isUpdating: string;
}
const Containee = (props: ContaineeProps) => <span>{props.name}</span>;
interface MapStateToProps {
isUpdating: boolean;
}
const mapStateToProps = state => ({ isUpdating: state.years.yearsUpdating });
const ConnectedContainee = connect<MapStateToProps, null, ContaineeProps>(mapStateToProps)(Containee);
interface Container {
name: string;
}
class Container extends React.Component<Container, {}> {
render() {
return (
<ConnectedContainee name="Fred" />
)
}
}
edit: This question is more about best practices in a redux/react/typescript application and less about the reason for this error. Do I always have to have required props type information specified in two places (props interface for the component, and the interface for the mapStateToProps function)?
The compile error you're receiving is correct. Given your ContaineeProps contains a property isUpdating, your component expects to be passed this property when you use it.
e.g. <ConnectedContainee name="Fred" isUpdating="testing" />
Given your question, I am wondering whetherisUpdating should be in ContaineeProps at all? If it's not then simply remove it and the compile error should go away.
If it is intended to be there then I would recommend giving it a different name as you already have an isUpdating prop in your MapStateToProps interface.
Update
If I understand your comment correctly you've added the isUpdating prop to your ContaineeProps to be able to access it within your component. To do this you don't need to add the property to both prop interfaces, I don't actually think that would work...
What you need to do is combine your interfaces so your component can have access to both interfaces that contain props passed in from parent components and props mapped in from redux.
Here's an example:
interface AllProps extends MapStateToProps, ContaineeProps {}
const Containee = (props: AllProps) => <span>{props.name} {props.isUpdating}</span>;
or
const Containee = (props: ContaineeProps & MapStateToProps) => <span>{props.name} {props.isUpdating}</span>;
props.isUpdating now refers to the property mapped in from the redux store. If you wanted to also store the isUpdating value locally you would use the components local state.