[pt.1] TypeScript: References for when you forget
[pt.1] TypeScript: References for when you forget

[pt.1] TypeScript: References for when you forget

Date
Feb 12, 2023
Tags
TypeScript
Front-end
React

Basic Prop Type Examples

A list of TypeScript types you will use in a React + TypeScript app:
 
type AppProps = { message: string; count: number; disabled: boolean; /** array of a type! */ names: string[]; phonenums: number[]; /** string literals to specify exact string values, with a union type to join them together */ status: "waiting" | "success"; /** an object with known properties (but could have more at runtime) */ obj: { id: string; title: string; }; /** array of objects! (common) */ objArr: { id: string; title: string; }[]; /** any non-primitive value - can't access any properties (NOT COMMON but useful as a placeholder) */ obj2: object; /** an interface with no required properties - (NOT COMMON, except for things like `React.Component<{}, State>`) */ obj3: {}; /** a dict object with any number of properties of the same type */ dict1: { [key: string]: MyTypeHere; }; dict2: Record<string, MyTypeHere>; // equivalent to dict1 /** function that doesn't take or return anything (VERY COMMON) */ onClick: () => void; /** function with named prop (VERY COMMON) */ onChange: (id: number) => void; /** function type syntax that takes an event (VERY COMMON) */ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; /** alternative function type syntax that takes an event (VERY COMMON) */ onClick(event: React.MouseEvent<HTMLButtonElement>): void; /** any function as long as you don't invoke it (not recommended) */ onSomething: Function; /** an optional prop (VERY COMMON!) */ optional?: OptionalType; /** when passing down the state setter function returned by `useState` to a child component. `number` is an example, swap out with whatever the type of your state */ setState: React.Dispatch<React.SetStateAction<number>>; };

Useful React Prop Type Examples

export declare interface AppProps { children?: React.ReactNode; // best, accepts everything React can render childrenElement: JSX.Element; // A single React element style?: React.CSSProperties; // to pass through style props onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref }
 

Helpful tips:

  • always use interface for public API's definition when authoring a library or 3rd party ambient type definitions
    • Q. Why?
      → Because this allows a consumer to extend them via declaration merging, if some definitions are missing.
  • consider using type for your React Component Props and State, for consistency and because it is more constrained.
 

Types vs interfaces:

✅ YUP
🚫 Nope
⚠️ In some cases
Aspect
Type
Interface
Can describe functions
Can describe constructors
Can describe tuples
Interfaces can extend it
⚠️
Classes can extend it
🚫
Classes can implement it (implements)
⚠️
Can intersect another one of its kind
⚠️
Can create a union with another one of its kind
🚫
Can be used to create mapped types
🚫
Can be mapped over with mapped types
Expands in error messages and logs
🚫
Can be augmented
🚫
Can be recursive
⚠️
 

Function Components

// Declaring type of props - see "Typing Component Props" for more examples type AppProps = { message: string; }; /* use `interface` if exporting so that consumers can extend */ // Easiest way to declare a Function Component; return type is inferred. const App = ({ message }: AppProps) => <div>{message}</div>; // you can choose to annotate the return type // so an error is raised if you accidentally return some other type const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>; // you can also inline the type declaration; // eliminates naming the prop types but looks repetitive const App = ({ message }: { message: string }) => <div>{message}</div>;
 

Hooks

 

useState

const [user, setUser] = useState<User | null>(null); // later... setUser(newUser);
 

useCallback

const memoizedCallback = useCallback( (param1: string, param2: number) => { console.log(param1, param2) return { ok: true } }, [...], ); /** * VSCode will show the following type: * const memoizedCallback: * (param1: string, param2: number) => { ok: boolean } */
In React >= 18, the function signature of useCallback changed to the following:
function useCallback<T extends Function>(callback: T, deps: DependencyList): T; // @ts-expect-error Parameter 'e' implicitly has 'any' type. useCallback((e) => {}, []); // Explicit 'any' type. useCallback((e: any) => {}, []);
 

useEffect / useLayoutEffect

Both of useEffectand useLayoutEffectare used for performing side effects and return an optional cleanup function which means if they don't deal with returning values, no types are necessary.
function DelayedEffect(props: { timerMs: number }) { const { timerMs } = props; useEffect(() => { setTimeout(() => { /* do stuff */ }, timerMs); }, [timerMs]); // better; use the void keyword to make sure you return undefined return null; }
 

useRef

In TypeScript, useRef returns a reference that is either read-only or mutable, depends on whether your type argument fully covers the initial value or not. Choose one that suits your use case.
 
  • immutable
function Foo() { // - If possible, prefer as specific as possible. For example, HTMLDivElement // is better than HTMLElement and way better than Element. // - Technical-wise, this returns RefObject<HTMLDivElement> // If you are sure that divRef.current will never be null, // it is also possible to use the non-null assertion operator !: const divRef = useRef<HTMLDivElement>(null!); useEffect(() => { // Note that ref.current may be null. This is expected, because you may // conditionally render the ref-ed element, or you may forgot to assign it if (!divRef.current) throw Error("divRef is not assigned"); // Now divRef.current is sure to be HTMLDivElement doSomethingWith(divRef.current); },[]); // Give the ref to an element so React can manage it for you return <div ref={divRef}>etc</div>; }
 
  • mutable value ref
function Foo() { // Technically, this returns MutableRefObject<number | null> const intervalRef = useRef<number | null>(null); // You manage the ref yourself (that's why it's called MutableRefObject!) useEffect(() => { intervalRef.current = setInterval(...); return () => clearInterval(intervalRef.current); }, []); // The ref is not passed to any element's "ref" prop return <button onClick={/* clearInterval the ref */}>Cancel timer</button>; }
 
 

References: