concise and readable syntax for props in a react component in typescript - reactjs

So if you declare a React.FC , then you get to do a type declaration and thus get to pass it props :
const FuntionalComponent: React.FC<personType> = ({ id, nationality, name, familyName, age, selected }) =>
<div>
...directly html
</div>
export default FuntionalComponent;
But you cannot declare any methods or use hooks there (I have not found a way)
Then there's the React.Component type :
class Component extends React.Component<{}, {Stateproperty: string}>{
constructor(){
}
hook(){
}
method(){
}
render() {
return (
<div>
...html finally
</div>
)
}
}
export default component;
as you can see I can pass a state but not a props.
If I try something like this :
class Component extends React.Component<{propsProperty: Array}, {Stateproperty: string}>{
and then add my propsProperty to my html :
<Component propsProperty={thisArray} />
Yet, they error out with the following entry:
TS2322: Type '{ propsProperty: any; }' is not assignable to type
'IntrinsicAttributes & { children?: ReactNode; }'.   Property
'tankData' does not exist on type 'IntrinsicAttributes & { children?:
ReactNode; }'.
These tutorials seem to indicate that there is no other way to declare components:
https://riptutorial.com/reactjs/example/25321/declare-default-props-and-proptypes
https://medium.com/#cristi.nord/props-and-how-to-pass-props-to-components-in-react-part-1-b4c257381654
I found this article about TypeScript errors in React:
https://medium.com/innovation-and-technology/deciphering-typescripts-react-errors-8704cc9ef402, but it didn't have my issue.
I also tried this solution : https://stackoverflow.com/a/47395464/4770754. Even though it's clearly not the same issue, it seemed somewhat close, yet it didn't help.
React docs are not helpful since they ignore TypeScript and are not human-readable.
I need a way that's both concise, respects TypeScript and allows for both props and methods within the class body. Does this not exist at all?

But you cannot declare any methods or use hooks there (I have not found a way)
A good standard way of declaring a FC is:
type ComponentProps = {
id: string,
nationality: string,
...
}
const MyComponent: React.FC<ComponentProps> = ({
id,
nationality,
...rest
}: ComponentProps) => {
const someMethod = () => {
console.log('I am console logging');
}
return(
<div>
{/* HERE YOU WILL RENDER STUFF */}
</div>
)
}
Note that in the above I deconstruct the props on instantiation so that id, nationality can be leveraged directly in the component.
I don't think you need to worry too much about the syntax highlighting until you're familiar with the above.

Related

Type react component with changeable wrapper and wrapper props

Learning TypeScript.
I'm trying add types to react component which accepts wrapper component and forward rest of the props to wrapper component. But getting following error:
Type 'Pick<DirectProps<WrapperProps> & WrapperProps, Exclude<keyof WrapperProps, "as">> & { children: string; }' is not assignable to type 'IntrinsicAttributes & WrapperProps & { children?: ReactNode; }'.
Type 'Pick<DirectProps<WrapperProps> & WrapperProps, Exclude<keyof WrapperProps, "as">> & { children: string; }' is not assignable to type 'WrapperProps'.
'WrapperProps' could be instantiated with an arbitrary type which could be unrelated to 'Pick<DirectProps<WrapperProps> & WrapperProps, Exclude<keyof WrapperProps, "as">> & { children: string; }'
Which I'm not able to solve.
Minimal example:
interface DirectProps<Props = unknown> {
as?: string | React.ComponentType<Props>;
}
function GenericComponent<Props = unknown>({
as: Component = "div",
...props
}: DirectProps<Props> & Props): JSX.Element {
return <Component {...props}>Here goes children</Component>;
}
Which is used like that:
{/* Render as div */}
<GenericComponent onClick={() => console.log("Click")} />
{/* Render as link */}
<GenericComponent<React.AnchorHTMLAttributes<HTMLAnchorElement>>
as="a"
href="https://stackoverflow.com"
target="_blank"
/>
I prepared codesandbox example:
https://codesandbox.io/s/sweet-hofstadter-2zzbe?file=/src/App.tsx
I'll be eternally grateful for help
first of all, you always pass a string to the props as, so we can change the interface to:
interface DirectProps {
as?: string;
}
after that we can change the function to
function GenericComponent<Props = React.HTMLAttributes<HTMLDivElement>>({
as: Component = "div",
...props
}: DirectProps & Props): JSX.Element {
return <Component {...props}/>;
}
Then the error goes away in the code sandbox you set up.
Part of it complaining was that you are adding your own children in the Component. The children would be automatically passed down through ...props
I'm not sure why you would want to build a generic component. I would suggest having atomic components. Your code will be more readable and easier to debug if the responsibilities of each component does one thing well. Ask yourself: What problem am I trying to solve exactly? How readable and debuggable is my code? Do I save myself any "thought complexity" by implementing generic components? How would I unit test these components?
Take the following atomic components, for example:
// components/Links.tsx
export function A(props: React.HTMLProps<HTMLAnchorElement>) {
return (<a {...props} style={{ ...customStylesOrWhatever }} />)
}
export function LinkToHome() {
return Home
}
export function LinkToSettings() {
return Settings
}
// "Dumb" components:
<LinkToHome />
<LinkToSettings />
StackOverflow
Imagine, for a moment, looking for a specific component in a tree full of GenericComponent. Personally, as a colleague, I would be super bummed out. In applications with thousands of components this would require significant "brain RAM". With atomic responsibilities, this becomes a trivial task. This is a decision on Composition vs. Configuration where Composition is typically preferred in React apps.

How to create and use a styled-component that renders {props.children} in Typescript?

I have an ExternalLink component that I use to render anchor tags for external URLs. I've implemented it using styled-components.
CodeSandbox link
ExternalLink.tsx
It basically gets an href prop and render the children as {props.children}.
const LinkAnchor = styled.a``;
interface ExternalLink_I {
className?: string;
href: string;
}
const ExternalLink: React.FC<ExternalLink_I> = (props) => {
console.log("Rendering ExternalLink...");
return (
<LinkAnchor
className={props.className} // ALLOW STYLING WITH STYLED-COMPONENTS
href={props.href}
target="_blank"
rel="noopener noreferrer"
>
{props.children}
</LinkAnchor>
);
};
But I'm getting an error when rendering this component with children.
App.tsx
export default function App() {
return (
<ExternalLink href={"https://www.google.com"}>
EXTERNAL_LINK
</ExternalLink>
);
}
Here is the error msg in text format:
Type '{ children: string; href: string; }' is not assignable to type 'IntrinsicAttributes & ExternalLink_I'.
Property 'children' does not exist on type 'IntrinsicAttributes & ExternalLink_I'.ts(2322)
QUESTION
What am I doing wrong? How can I get rid of this?
You should add children type too:
interface ExternalLink_I {
...
// or React.element, React.ReactNode etc
children: string;
}
You forgot to mention that the ExternalLink component was wrapped in React.memo. The 'children' prop has been known to cause issues with React.memo, hence removed from the type definitions for the component. Also looking at your code, doesn't seem to be making any big optimisations. Removing it would fix the type error, or you could choose to ignore it.
Here is some light reading regarding this issue:
Q: When should you NOT use React memo? #14463
Why using the children prop makes React.memo() not work

How to isolate known properties in an intersection of a generic type and a non-generic type

I have an HOC that takes a withPaper prop but does not pass it to the component it will render.
import React, { ComponentType, FC } from "react";
import { Paper } from "#material-ui/core";
interface WithOptionalPaperProps {
withPaper?: boolean;
}
export const withOptionalPaper = <Props extends object>() => (
Component: ComponentType<Props>
) => ({ withPaper, ...otherProps }: Props & WithOptionalPaperProps) => {
if (withPaper) {
return (
<Paper>
<Component {...otherProps as Props} />
</Paper>
);
}
return <Component {...otherProps as Props} />;
};
// Code below shows how the code above will be used.
interface NonPaperedComponentProps {
text: string;
className: string;
}
const NonPaperedComponent: FC<NonPaperedComponentProps> = props => {
return <h1 className={props.className}>{props.text}</h1>;
};
// Code will be used like an HOC.
// 'withPaper' prop can be optionally added to wrap the underlying component in 'Paper'
const OptionalPaperedComponent = withOptionalPaper<NonPaperedComponentProps>()(
NonPaperedComponent
);
// All props except 'withPaper' should be passed to 'NonPaperedComponent'
const renderedComponent = (
<OptionalPaperedComponent withPaper className="Hello" text="Hello There" />
);
I have removed the errors by type casting with otherProps as Props. Without them it produces the error 'Props' could be instantiated with a different subtype of constraint 'object'
https://codesandbox.io/s/gallant-shamir-z2098?file=/src/App.tsx:399-400
I would have assumed that since I have destructured and isolated the known properties from Props & WithOptionalPaperProps the types would look like this:
{
withPaper, // type 'WithOptionalPaperProps["withPaper"]'
...otherProps // type 'Props'
}
How do I make it that the Component the withOptionalPaper returns with a withPaper prop without passing it to its children but still passing all the other props?
This is a limitation in how de-structured rest objects are types. For a long type TS did not even allow de-structuring of generic type parameters. In version 3.2 the ability to use rest variables with generic type parameters was added (PR) but the rest variable is typed as Pick<T, Exclude<keyof T, "other" | "props">>, or equivalently Omit<T, "other" | "props">. The use of the conditional type Exclude will work fine for the consumers of this function if T is fully resolved (ie not a generic type parameter) but inside the function, typescript can't really reason about the type that contains the Exclude. This is just a limitation of how conditional types work. You are excluding from T, but since T is not known, ts will defer the evaluation of the conditional type. This means that T will not be assignable to Pick<T, Exclude<keyof T, "other" | "props">>
We can use a type assertion as you have, and this is what I have recommended in the past. Type assertions should be avoided, but they are there to help out when you (ie the developer) have more information than the compiler. This is one of those cases.
For a better workaround we could use a trick. While Omit<T, "props"> is not assignable to T it is assignable to itself. So we can type the component props as Props | Omit<Props, "withPaper">. Since Props and Omit<Props, "withPaper"> are essentially the same type, this will not matter much, but it will let the compiler assign the rest object to the component props.
export const withOptionalPaper = <Props extends object>(
Component: ComponentType<Props | Omit<Props & WithOptionalPaperProps, keyof WithOptionalPaperProps>>
) => ( {withPaper, ...otherProps }: Props & WithOptionalPaperProps) => {
if (withPaper) {
return (
<Paper>
<Component {...otherProps} />
</Paper>
);
}
return <Component {...otherProps } />;
};
Playground Link

Enforcing TSX prop type inline for function declaration which declares props using object destructuring

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

React & Typescript component props type for `Component`

I have the following HOC in React:
`restricted` prop
const ConditionalSwitch = ({component: Component, showIfTrue, ...rest}) => (
showIfTrue
? <Component {...rest}/>
: null
);
How do I define the props so that Typescript will be happy with it?
{component: Component, showIfTrue, ...rest}
I tried
export interface CSProps {
component: any,
showIfTrue: boolean
}
How do I handle the ...rest here?
If you want to preserve type safety, you need to make ConditionalSwitch generic and have it infer the full props passed to the component based on the actual value of Component. This will ensure that the client of ConditionalSwitch will pass in all the required properties of the used component. To do this we use the approach described here:
const ConditionalSwitch = <C extends React.ComponentType<any>>({ Component, showIfTrue, ...rest}: { Component: C, showIfTrue: boolean} & React.ComponentProps<C> ) => (
showIfTrue
? <Component {...(rest as any) }/>
: null
);
function TestComp({ title, text}: {title: string, text: string}) {
return null!;
}
let e = <ConditionalSwitch Component={TestComp} showIfTrue={true} title="aa" text="aa" /> // title and text are checked
When passing the rest to the component we do need to use a type assertion because typescript is not able to figure out that { Component: C, showIfTrue: boolean} & React.ComponentProps<C> minus Component and showIfTrue is just React.ComponentProps<C> but you can't have it all :)
Try this:
export interface CSProps {
component: any;
showIfTrue: boolean;
[key: string]: any;
}

Resources