typescript-cheatsheets / react

Cheatsheets for experienced React developers getting started with TypeScript

Home Page:https://react-typescript-cheatsheet.netlify.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

awesome advice from Ferdaber

ferdaber opened this issue · comments

Hi! I'm one of the contributors/maintainers to the @react/types library in DT, and I just have some suggested changes to the docs to ensure that folks who start with TS in React has a smoother experience!

For the section on Function Components:

  • A common pitfall is that these patterns are not supported:
const MyConditionalComponent = ({ shouldRender = false }) => shouldRender ? <div /> : false
const MyArrayComponent = () => Array(5).fill(<div />)
const el = <MyConditionalComponent /> // throws an error
const el2 = <MyArrayComponentt /> // throws an error

This is because due to limitations in the compiler, function components cannot return anything other than a JSX expression or null, otherwise it complains with a cryptic error message saying that the other type is not assignable to Element. Unfortunately just annotating the function type will not help so if you really need to return other exotic types that React supports, you'd need to perform a type assertion:

const MyArrayComponent = () => Array(5).fill(<div />) as any as JSX.Element

For the section on Class Components:

  • I recommend annotating the state class property in addition to adding it as the 2nd generic type parameter in the base class, because it allows better type inference when accessing this.state and also initializing the state. This is because they work in two different ways, the 2nd generic type parameter will allow this.setState() to work correctly, because that method comes from the base class, but initializing state inside the component overrides the base implementation so you have to make sure that you tell the compiler that you're not actually doing anything different.
type MyState = {}
class App extends React.Component<{}, MyState> { state: MyState = {} }

For the section on Typing DefaultProps:

  • I strongly do not recommend annotating defaultProps into a Partial of your Props interface. This causes issues that are a bit complex to explain here with the type inference working with JSX.LibraryManagedAttributes. Basically it causes the compiler to think that when creating a JSX expression with that component, that all of its props are optional. Don't do this! Instead this pattern is recommended:
type Props = Required<typeof MyComponent.defaultProps> & { /* additional props here */ }

export class MyComponent extends React.Component<Props> {
  static defaultProps = {
    foo: 'foo'
  }
}

For the section on Forms and Events:

  • I would just add that inlining the event handler when you don't need to optimize is recommended for better type inference:
// instead of this:
const myHandler = (event: React.MouseEvent<HTMLButtonElement>) => {}
const el = <button onClick={myHandler} />

// do this:
const el = <button onClick={event => {}} />

This tells the compiler that there is no ambiguity to when the handler is being used, and adding function types to the call site allows it to infer the event parameter's types right away.

That's all for now. I can definitely make a PR if y'all agree to these changes!

hey Ferdy! wow, thank you for maintaining @types/react and giving feedback! no PR needed, i'll incorporate your advice myself.

may i ask - how did you get started contributing to @types/react and are there any resources you recommend reading on it? (especially historical/meta articles on how @types/react has evolved, is managed, and lessons we have learned) I feel like its basically undocumented because i couldnt find anything good on it - and if theres one thing i can do well i can help you write documentation. do you need more contributors?

I started when TS 3.0 was in the works and LibraryManagedAttributes were added as a feature (I implemented it in the React types with a lot of help from existing contributors and folks from the TS team).

One of the contributors is working on creating a large breaking change (potentially) to make the types much stronger, but at the cost of potentially breaking a lot of existing code, so she is working through the kinks on that.

As far as historical info goes, there's not much honestly, a lot of "this happened or was implemented because X" is scattered among closed issues in the DT repository. It would be nice to document why things are the way they are, but it may not be worth it if a lot of it is going away soon.

That said, I really like this repository as a place to build off of because there's not much out there that documents best practices with using Typescript and JSX (and with React, specifically). I really like this section of the TS handbook as a base to get really familiar with how the compiler works with JSX: https://www.typescriptlang.org/docs/handbook/jsx.html, a lot of why things are done they way they are in the React types spins off of these behaviors.

ok cool. yeah i'll try to keep this a tightly scoped best practices doc.

what issue is this large breaking change? would love to check it out.

microsoft/TypeScript#28954
which is dependent on some better resolution on microsoft/TypeScript#14729 (or at least being able to appropriately inject a generic type parameter into JSX.Element)

Hi @sw-yx @ferdaber ,
I had a doubt regarding on of the pitfalls that you mention.
You mention to use as JSX.Element while currently the Readme mentions React.ReactNode as the best option.
Are there any differences between the two? Should we use JSX.Element where we are targetting interoperability between react and preact? Or JSX.Element is the generic term for all VDOM libraries and React/Preact Developers should use React.ReactNode only.

Ok, I realize JSX.Element does not contain string. So I guess React.ReactNode is the better option.

good :)

A more technical explanation is that a valid React node is not the same thing as what is returned by React.createElement. Regardless of what a component ends up rendering, React.createElement always returns an object, which is the JSX.Element interface, but React.ReactNode is the set of all possible return values of a component.

  • JSX.Element -> Return value of React.createElement
  • React.ReactNode -> Return value of a component

putting that in

@sw-yx there was a weird grammatical error in my post above which is quoted, which I have corrected. Can you fix inside the README? 😅

done. im also just gonna invite you to help maintain this repo if you want :)

Another alternative to using the type casting for this: const MyArrayComponent = () => Array(5).fill(<div />) as any as JSX.Element, is to use a fragment.

const MyArrayComponent = () => <>{Array(5).fill(<div />)}</>
commented

Yeah kind of shocked to see anything that advocates for typescript also recommending throwing in as any as ....