Skip to content

milespratt/simple-react-typescript-cheatsheet

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 

Repository files navigation

Super Simple React Typescript Cheatsheet

Typing props with inline type

function Button(props: { children: React.ReactNode }) {
  return <button>{props.children}</button>;
}

Typing props with Type

// you can use interface too
type ButtonProps = {
  className: string;
  children: React.ReactNode;
};

function Button(props: ButtonProps) {
  return <button className={props.className}>{props.children}</button>;
}

// with destructuring
function OtherButton({ className, ...props }: ButtonProps) {
  return <button className={className}>{props.children}</button>;
}

Typing props with default value

type ButtonProps = {
  disabled?: boolean;
  className: string;
  children: React.ReactNode;
};

function Button({ disabled = true, ...props }: ButtonProps) {
  return (
    <button disabled={disabled} {...props}>
      {props.children}
    </button>
  );
}

Typing props with children

type ButtonProps = {
  // accept everything React can render
  children: React.ReactNode;
};

function Button(props: ButtonProps) {
  return <button>{props.children}</button>;
}

Using Native HTML props to React Components

1. Basic

import React, { ComponentProps } from "react";

//ComponentProps<"button"> : get all type/props from native button element

function Button(props: ComponentProps<"button">) {
  return <button>{props.children}</button>;
}

2. Combine with your type

import React, { ComponentProps } from "react";

type ButtonProps = ComponentProps<"button"> & {
  variant: "primary" | "secondary";
};

function Button(props: ButtonProps) {
  return <button {...props}>{props.children}</button>;
}

3. Overriding Native Props

import React, { ComponentProps } from "react";

//remove onChange property from input with Omit<Type, Keys> and combine with new type
type InputProps = Omit<ComponentProps<"input">, "onChange"> & {
  onChange: (value: string) => void;
};

function Input(props: InputProps) {
  return <input {...props} />;
}

4. Extracting Props from Custom Components

useful when author of some external library dont export the type definition

import { ComponentProps } from "react";
import { Navbar } from "some-ui-library";

type NavBarProps = ComponentProps<typeof NavBar>;

Typing Event Handlers from native element

hover native html props, you can copy paste the type definition

type ButtonProps = {
  other?: Boolean;
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
};

event handler

useState

// ❌ Typescript already know `text` type is string
const [text, setText] = useState<string>("");
// ✅ no need to tell typescript
const [text, setText] = useState("");
type Tag = {
  id: number;
  value: string;
};

const [tags, setTags] = useState<Tag[]>([]);
// data : Data | undefined
const [data, setData] = useState<Data>();
// data : Data | undefined
const [data, setData] = useState<Data>(undefined);
// data : Data | null
const [data, setData] = useState<Data | null>(null);

useCallback

function App(props: { id: number }) {
  const handleClick = useCallback(
    //⬇️ add type here
    (message: string) => {
      console.log("name");
    },
    [props.id]
  );

  return (
    <div>
      <p>{message}</p>
      <button onClick={() => handleClick("hello")}>button 1</button>
      <button onClick={() => handleClick("hello")}>button 2</button>
    </div>
  );
}

useRef

Basic useRef

export const Component = () => {
  // pass type if it doesn't have initial value
  const id1 = useRef<string>();
  // no need to pass type if it have initial value
  const id2 = useRef("");

  useEffect(() => {
    id1.current = "Random value!";
  }, []);

  return <div></div>;
};

useRef with HTML element

export const Component = () => {
  // add null to initial value
  const ref = useRef<HTMLDivElement>(null);

  return <div ref={ref} />;
};
export const Component = () => {
  // add null to initial value
  const ref = useRef<HTMLInputElement>(null);

  return <input ref={ref} />;
};

useRef with forwardRef

type InputProps = {
  className: string;
};

const Search = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return <input ref={ref} className={props.className} />;
});
// add displayName if you are using function expression, so its has a name in React Devtool
Search.displayName = "Search";

function App() {
  const input = React.useRef<HTMLInputElement>(null);

  useEffect(() => {
    // focus to input element on first render
    if (input.current) {
      input.current.focus();
    }
  }, []);

  return <Search className="some-input" ref={input} />;
}

Making a Read-Only Ref Mutable

export const Component = () => {
  const ref1 = useRef<string>(null);
  // if you pass null to initial value
  // this not allowed to change directly
  ref1.current = "Hello";

  const ref2 = useRef<string>();
  // if initial value is undefined this is allowed to change (mutable)
  ref2.current = "Hello";

  return null;
};

useReducer

You can use Discriminated Unions for reducer actions. Don't forget to define the return type of reducer, otherwise TypeScript will infer it.

import { useReducer } from "react";

const initialState = { count: 0 };

type ACTIONTYPE =
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: string };

function reducer(state: typeof initialState, action: ACTIONTYPE) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
        -
      </button>
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
    </>
  );
}

Context

import { createContext, useState } from "react";

type ThemeContextType = "light" | "dark";

const ThemeContext = createContext<ThemeContextType>("light");

// If you don't have any meaningful default value, specify null:
const _ThemeContext = createContext<ThemeContextType | null>(null);

const App = () => {
  const [theme, setTheme] = useState<ThemeContextType>("light");

  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  );
};

Polymorphic

Imagine a Button component that renders a <button> element, but with your fancy button styles. If want to render the Button component as an a element we might have an API like:

<Button variantColor="primary" href="https://blog.makerx.com.au/" as="a">
  Click me
</Button>

This looks nice, but its not work well with typescript. here the alternative using radix-ui/react-slot

// Button.tsx
import { Slot } from '@radix-ui/react-slot'

type ButtonProps = React.ComponentPropsWithoutRef<'button'> & {
  variantColor: 'primary' | 'secondary' | 'danger'
  asChild?: boolean
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, forwardedRef) => {
  const { variantColor, asChild, ...buttonProps } = props
  const Component = (asChild ? Slot : 'button') as 'button'

  return (
    <Component
      {...buttonProps}
      ref={forwardedRef}
      className={clsx(
          // ...
      )}
    />
  )
}

// App.tsx

function App() {
  return (
    <div>
       {/* asChild must be true */}
      <Button variantColor="primary" asChild>
        {/* render button component as link */}
        <a href="https://google.com">About</a>
      </Button>
    </div>
  )
}

Types or Interfaces?

interfaces are different from types in TypeScript, but they can be used for very similar things as far as common React uses cases are concerned. Here's a helpful rule of thumb:

  • Always use interface for public API's definition when authoring a library or 3rd party ambient type definitions.

  • Consider using type for your React Component Props and State, because it is more constrained.

Types are useful for union types (e.g. type MyType = TypeA | TypeB) whereas Interfaces are better for declaring dictionary shapes and then implementing or extending them.

Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published