diff --git a/package.json b/package.json index 44ae77c..ff03223 100644 --- a/package.json +++ b/package.json @@ -191,4 +191,4 @@ "zod": "^3.21.4" }, "type": "module" -} +} \ No newline at end of file diff --git a/src/04-advanced-props/22-discriminated-union-props.problem.tsx b/src/04-advanced-props/22-discriminated-union-props.problem.tsx index 6116410..274db32 100644 --- a/src/04-advanced-props/22-discriminated-union-props.problem.tsx +++ b/src/04-advanced-props/22-discriminated-union-props.problem.tsx @@ -8,11 +8,17 @@ * impossible combinations of props. */ -type ModalProps = { - variant: "no-title" | "title"; - title?: string; +type ModalWithTitle = { + variant: "title"; + title: string; }; +type ModalNoTitle = { + variant: "no-title"; +}; + +type ModalProps = ModalWithTitle | ModalNoTitle; + export const Modal = (props: ModalProps) => { if (props.variant === "no-title") { return
No title
; diff --git a/src/04-advanced-props/23-destructuring-discriminated-unions.problem.tsx b/src/04-advanced-props/23-destructuring-discriminated-unions.problem.tsx index 5a2c1c0..ecccc76 100644 --- a/src/04-advanced-props/23-destructuring-discriminated-unions.problem.tsx +++ b/src/04-advanced-props/23-destructuring-discriminated-unions.problem.tsx @@ -16,11 +16,11 @@ type ModalProps = title: string; }; -export const Modal = ({ variant, title }: ModalProps) => { - if (variant === "no-title") { +export const Modal = ({ props }: ModalProps) => { + if (props.variant === "no-title") { return
No title
; } else { - return
Title: {title}
; + return
Title: {props.title}
; } }; diff --git a/src/04-advanced-props/24-discriminated-union-with-other-props.problem.tsx b/src/04-advanced-props/24-discriminated-union-with-other-props.problem.tsx index 358bc67..11dc37d 100644 --- a/src/04-advanced-props/24-discriminated-union-with-other-props.problem.tsx +++ b/src/04-advanced-props/24-discriminated-union-with-other-props.problem.tsx @@ -1,11 +1,14 @@ -type ModalProps = +type ModalProps = ( | { variant: "no-title"; } | { variant: "title"; title: string; - }; + } +) & { + buttonColor: string; +}; /** * 1. How do we add a `buttonColor` prop to the `ModalProps` type that is diff --git a/src/04-advanced-props/25-toggle-props.problem.tsx b/src/04-advanced-props/25-toggle-props.problem.tsx index b3fe172..ca3dbcc 100644 --- a/src/04-advanced-props/25-toggle-props.problem.tsx +++ b/src/04-advanced-props/25-toggle-props.problem.tsx @@ -11,11 +11,15 @@ * Hint - you'll need a discriminated union! */ -type EmbeddedPlaygroundProps = { - useStackblitz?: boolean; - stackblitzId?: string; - codeSandboxId?: string; -}; +type EmbeddedPlaygroundProps = + | { + useStackblitz: true; + stackblitzId: string; + } + | { + useStackblitz?: false; + codeSandboxId?: string; + }; const EmbeddedPlayground = (props: EmbeddedPlaygroundProps) => { if (props.useStackblitz) { diff --git a/src/04-advanced-props/27-either-all-these-props-or-none.problem.tsx b/src/04-advanced-props/27-either-all-these-props-or-none.problem.tsx index 147cac7..c9efdf6 100644 --- a/src/04-advanced-props/27-either-all-these-props-or-none.problem.tsx +++ b/src/04-advanced-props/27-either-all-these-props-or-none.problem.tsx @@ -15,7 +15,10 @@ type InputProps = ( value: string; onChange: ChangeEventHandler; } - | {} + | { + value?: undefined; + onChange?: undefined; + } ) & { label: string; }; diff --git a/src/04-advanced-props/28-passing-react-components-vs-passing-react-nodes.problem.tsx b/src/04-advanced-props/28-passing-react-components-vs-passing-react-nodes.problem.tsx index 862a470..4ff6071 100644 --- a/src/04-advanced-props/28-passing-react-components-vs-passing-react-nodes.problem.tsx +++ b/src/04-advanced-props/28-passing-react-components-vs-passing-react-nodes.problem.tsx @@ -8,7 +8,7 @@ import { Equal, Expect } from "../helpers/type-utils"; * find a way to fix them by changing the definition of TableProps. */ interface TableProps { - renderRow: React.ReactNode; + renderRow: (index: number) => React.ReactNode; } const Table = (props: TableProps) => { diff --git a/src/04-advanced-props/29-variants-with-classnames.problem.tsx b/src/04-advanced-props/29-variants-with-classnames.problem.tsx index 9d79660..617d4ed 100644 --- a/src/04-advanced-props/29-variants-with-classnames.problem.tsx +++ b/src/04-advanced-props/29-variants-with-classnames.problem.tsx @@ -14,7 +14,7 @@ type ButtonProps = { * * Hint: you'll need 'typeof' and 'keyof'. */ - variant: "primary" | "secondary" | "success"; + variant: keyof typeof classNamesMap; }; export const Button = (props: ButtonProps) => { diff --git a/src/04-advanced-props/30-partial-autocomplete.problem.tsx b/src/04-advanced-props/30-partial-autocomplete.problem.tsx index 55b2232..94f0274 100644 --- a/src/04-advanced-props/30-partial-autocomplete.problem.tsx +++ b/src/04-advanced-props/30-partial-autocomplete.problem.tsx @@ -13,7 +13,7 @@ type Size = keyof typeof presetSizes; * a Size. But there's an issue (see below). */ -type LooseSize = Size | string; +type LooseSize = Size | (string & {}); export const Icon = (props: { size: LooseSize }) => { return ( diff --git a/src/04-advanced-props/31-as-const.problem.ts b/src/04-advanced-props/31-as-const.problem.ts index 9440b74..e4d5bfe 100644 --- a/src/04-advanced-props/31-as-const.problem.ts +++ b/src/04-advanced-props/31-as-const.problem.ts @@ -4,10 +4,10 @@ const BACKEND_TO_FRONTEND_STATUS_MAP = { 0: "pending", 1: "success", 2: "error", -}; +} as const; -type BackendStatus = unknown; -type FrontendStatus = unknown; +type BackendStatus = keyof typeof BACKEND_TO_FRONTEND_STATUS_MAP; +type FrontendStatus = (typeof BACKEND_TO_FRONTEND_STATUS_MAP)[BackendStatus]; type test = [ Expect>, diff --git a/src/04-advanced-props/32-satisfies-vs-annotation-vs-as.problem.tsx b/src/04-advanced-props/32-satisfies-vs-annotation-vs-as.problem.tsx index dc99375..9141ec0 100644 --- a/src/04-advanced-props/32-satisfies-vs-annotation-vs-as.problem.tsx +++ b/src/04-advanced-props/32-satisfies-vs-annotation-vs-as.problem.tsx @@ -5,7 +5,7 @@ const buttonProps = { type: "button", // @ts-expect-error illegalProperty: "I AM ILLEGAL", -}; +} satisfies ComponentProps<"button">; <> diff --git a/src/04-advanced-props/33-prop-groups-with-variants.problem.tsx b/src/04-advanced-props/33-prop-groups-with-variants.problem.tsx index e37923d..b7b6d42 100644 --- a/src/04-advanced-props/33-prop-groups-with-variants.problem.tsx +++ b/src/04-advanced-props/33-prop-groups-with-variants.problem.tsx @@ -19,7 +19,7 @@ const buttonPropsMap = { // @ts-expect-error illegalProperty: "whatever", }, -}; +} satisfies Record>; type ButtonProps = { variant: keyof typeof buttonPropsMap; diff --git a/src/05-generics/34-type-helpers.problem.tsx b/src/05-generics/34-type-helpers.problem.tsx index 5ff08f7..ca86711 100644 --- a/src/05-generics/34-type-helpers.problem.tsx +++ b/src/05-generics/34-type-helpers.problem.tsx @@ -1,9 +1,11 @@ type Icon = "home" | "settings" | "about"; type ButtonVariant = "primary" | "secondary" | "tertiary"; +type EmptyValue = T | (string & {}); + // How do we refactor this to make it DRY? -type LooseIcon = Icon | (string & {}); -type LooseButtonVariant = ButtonVariant | (string & {}); +type LooseIcon = EmptyValue; +type LooseButtonVariant = EmptyValue; export const icons: LooseIcon[] = [ "home", diff --git a/src/05-generics/35-type-helpers-2.problem.tsx b/src/05-generics/35-type-helpers-2.problem.tsx index e7471d5..66572f5 100644 --- a/src/05-generics/35-type-helpers-2.problem.tsx +++ b/src/05-generics/35-type-helpers-2.problem.tsx @@ -8,16 +8,17 @@ import { ChangeEventHandler } from "react"; * and returns it along with a union with all of its * keys turned to undefined. */ -export type InputProps = ( - | { - value: string; - onChange: ChangeEventHandler; - } + +type MakeOptional = + | T | { - value?: undefined; - onChange?: undefined; - } -) & { + [K in keyof T]?: undefined; + }; + +export type InputProps = MakeOptional<{ + value: string; + onChange: ChangeEventHandler; +}> & { label: string; }; diff --git a/src/05-generics/36-type-helpers-with-constraints.problem.tsx b/src/05-generics/36-type-helpers-with-constraints.problem.tsx index 920f0fc..1bf3c41 100644 --- a/src/05-generics/36-type-helpers-with-constraints.problem.tsx +++ b/src/05-generics/36-type-helpers-with-constraints.problem.tsx @@ -1,6 +1,6 @@ import { Equal, Expect } from "../helpers/type-utils"; -type AllOrNothing = T | ToUndefinedObject; +type AllOrNothing = T | ToUndefinedObject; type ToUndefinedObject = Partial>; diff --git a/src/05-generics/37-generic-localstorage-hook.problem.ts b/src/05-generics/37-generic-localstorage-hook.problem.ts index b368977..04c9fad 100644 --- a/src/05-generics/37-generic-localstorage-hook.problem.ts +++ b/src/05-generics/37-generic-localstorage-hook.problem.ts @@ -14,12 +14,12 @@ import { Equal, Expect } from "../helpers/type-utils"; * * 1. Figure out a way to make this work using generics. */ -export const useLocalStorage = (prefix: string) => { +export const useLocalStorage = (prefix: string) => { return { - get: (key: string) => { + get: (key: string): T | null => { return JSON.parse(window.localStorage.getItem(prefix + key) || "null"); }, - set: (key: string, value: any) => { + set: (key: string, value: T): void => { window.localStorage.setItem(prefix + key, JSON.stringify(value)); }, }; @@ -39,6 +39,6 @@ it("Should not let you set a value that is not the same type as the type argumen user.set( "something", // @ts-expect-error - {}, + {} ); }); diff --git a/src/05-generics/38-generic-hooks.problem.ts b/src/05-generics/38-generic-hooks.problem.ts index 716bbd9..6747410 100644 --- a/src/05-generics/38-generic-hooks.problem.ts +++ b/src/05-generics/38-generic-hooks.problem.ts @@ -8,7 +8,7 @@ import { Equal, Expect } from "../helpers/type-utils"; * * There are _many_ different solutions - but they all involve generics. */ -export const useStateAsObject = (initial: any) => { +export const useStateAsObject = (initial: T) => { const [value, set] = useState(initial); return { @@ -26,12 +26,12 @@ type ExampleTests = [ typeof example.set, React.Dispatch> > - >, + > ]; const num = useStateAsObject(2); type NumTests = [ Expect>, - Expect>>>, + Expect>>> ]; diff --git a/src/05-generics/39-generic-props.problem.tsx b/src/05-generics/39-generic-props.problem.tsx index 20a3633..cf15a0c 100644 --- a/src/05-generics/39-generic-props.problem.tsx +++ b/src/05-generics/39-generic-props.problem.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -interface TableProps { - rows: any[]; - renderRow: (row: any) => ReactNode; +interface TableProps { + rows: T[]; + renderRow: (row: T) => ReactNode; } /** @@ -12,7 +12,7 @@ interface TableProps { * generic. It's just `any`. We want to make it generic so that the type of * the data is inferred from the `rows` prop. */ -export const Table = (props: TableProps) => { +export const Table = (props: TableProps) => { return ( diff --git a/src/05-generics/40-generic-class-components.problem.tsx b/src/05-generics/40-generic-class-components.problem.tsx index 774fccb..ad4c730 100644 --- a/src/05-generics/40-generic-class-components.problem.tsx +++ b/src/05-generics/40-generic-class-components.problem.tsx @@ -1,12 +1,12 @@ import React, { ReactNode } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -interface TableProps { - rows: any[]; - renderRow: (row: any) => ReactNode; +interface TableProps { + rows: T[]; + renderRow: (row: T) => ReactNode; } -export class Table extends React.Component { +export class Table extends React.Component> { render(): ReactNode { return (
diff --git a/src/05-generics/41-passing-types-to-components.problem.tsx b/src/05-generics/41-passing-types-to-components.problem.tsx index d272c49..e54dd0e 100644 --- a/src/05-generics/41-passing-types-to-components.problem.tsx +++ b/src/05-generics/41-passing-types-to-components.problem.tsx @@ -25,7 +25,7 @@ interface User { } <> -
// @ts-expect-error rows should be User[] rows={[1, 2, 3]} renderRow={(row) => { @@ -33,7 +33,7 @@ interface User { return ; }} /> -
{row.name}
rows={[ { id: 1, diff --git a/src/05-generics/42-generic-inference-through-multiple-helpers.problem.tsx b/src/05-generics/42-generic-inference-through-multiple-helpers.problem.tsx index f9a7008..250dfe0 100644 --- a/src/05-generics/42-generic-inference-through-multiple-helpers.problem.tsx +++ b/src/05-generics/42-generic-inference-through-multiple-helpers.problem.tsx @@ -1,13 +1,13 @@ import { Equal, Expect } from "../helpers/type-utils"; -interface Button { - value: string; +interface Button { + value: TValue; label: string; } -interface ButtonGroupProps { - buttons: Button[]; - onClick: (value: string) => void; +interface ButtonGroupProps { + buttons: Button[]; + onClick: (value: T) => void; } /** @@ -19,7 +19,9 @@ interface ButtonGroupProps { * * 1. Try to solve this problem using generics. */ -const ButtonGroup = (props: ButtonGroupProps) => { +const ButtonGroup = ( + props: ButtonGroupProps +) => { return (
{props.buttons.map((button) => { diff --git a/src/05-generics/43-inference-flow-with-constraints.problem.ts b/src/05-generics/43-inference-flow-with-constraints.problem.ts index 9aae30b..da89c62 100644 --- a/src/05-generics/43-inference-flow-with-constraints.problem.ts +++ b/src/05-generics/43-inference-flow-with-constraints.problem.ts @@ -2,18 +2,22 @@ import { createUser } from "fake-external-lib"; import { useState } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -type Mutation = (...args: any[]) => Promise; +type Mutation = ( + ...args: TArgs +) => Promise; -interface UseMutationReturn { - mutate: Mutation; +interface UseMutationReturn { + mutate: Mutation; isLoading: boolean; } -interface UseMutationOptions { - mutation: Mutation; +interface UseMutationOptions { + mutation: Mutation; } -export const useMutation = (opts: UseMutationOptions): UseMutationReturn => { +export const useMutation = ( + opts: UseMutationOptions +): UseMutationReturn => { const [isLoading, setIsLoading] = useState(false); return { @@ -51,7 +55,7 @@ mutation.mutate( throwOnError: true, // @ts-expect-error extra prop extra: "oh dear", - }, + } ); type test = [ @@ -63,12 +67,12 @@ type test = [ user: { name: string; email: string }, opts?: { throwOnError?: boolean; - }, + } ) => Promise<{ id: string; name: string; email: string; }> > - >, + > ]; diff --git a/src/05-generics/44-generics-vs-discriminated-unions.problem.tsx b/src/05-generics/44-generics-vs-discriminated-unions.problem.tsx index 2a9fa5a..7ab229f 100644 --- a/src/05-generics/44-generics-vs-discriminated-unions.problem.tsx +++ b/src/05-generics/44-generics-vs-discriminated-unions.problem.tsx @@ -12,23 +12,35 @@ * * 2. There's a way of writing this type (and the component!) without * generics that's much simpler. Try to figure out how to do that. + * */ -export type ModalProps = { - isOpen: boolean; - variant: TVariant; -} & (TVariant extends "with-button" - ? { +type ModalProps = ( + | { + variant: "with-button"; buttonLabel: string; onButtonClick: () => void; } - : {}); + | { + variant: "without-button"; + } +) & { + isOpen: boolean; +}; + +// export type ModalProps = { +// isOpen: boolean; +// variant: TVariant; +// } & (TVariant extends "with-button" +// ? { +// buttonLabel: string; +// onButtonClick: () => void; +// } +// : {}); export type PossibleVariants = "with-button" | "without-button"; -export const Modal = ( - props: ModalProps, -) => { +export const Modal = (props: ModalProps) => { // ... return null; }; diff --git a/src/06-advanced-hooks/45-tuple-return-type.problem.ts b/src/06-advanced-hooks/45-tuple-return-type.problem.ts index 92c610f..5e7cd5d 100644 --- a/src/06-advanced-hooks/45-tuple-return-type.problem.ts +++ b/src/06-advanced-hooks/45-tuple-return-type.problem.ts @@ -11,7 +11,9 @@ import { Equal, Expect } from "../helpers/type-utils"; * 1. Find a way to fix the errors below. */ -export const useId = (defaultId: string) => { +export const useId = ( + defaultId: string +): [string, React.Dispatch>] => { const [id, setId] = useState(defaultId); return [id, setId]; @@ -21,5 +23,5 @@ const [id, setId] = useId("1"); type tests = [ Expect>, - Expect>>>, + Expect>>> ]; diff --git a/src/06-advanced-hooks/46-required-context.problem.tsx b/src/06-advanced-hooks/46-required-context.problem.tsx index 1b0726b..197dce2 100644 --- a/src/06-advanced-hooks/46-required-context.problem.tsx +++ b/src/06-advanced-hooks/46-required-context.problem.tsx @@ -14,8 +14,8 @@ import { Equal, Expect } from "../helpers/type-utils"; * * 1. See if you can fix it! */ -const createRequiredContext = () => { - const context = React.createContext(null); +const createRequiredContext = () => { + const context = React.createContext(null); const useContext = () => { const contextValue = React.useContext(context); @@ -27,7 +27,7 @@ const createRequiredContext = () => { return contextValue; }; - return [useContext, context.Provider]; + return [useContext, context.Provider] as const; }; const [useUser, UserProvider] = createRequiredContext<{ diff --git a/src/06-advanced-hooks/47-unions-in-usestate.problem.tsx b/src/06-advanced-hooks/47-unions-in-usestate.problem.tsx index e34471c..d4d9a9d 100644 --- a/src/06-advanced-hooks/47-unions-in-usestate.problem.tsx +++ b/src/06-advanced-hooks/47-unions-in-usestate.problem.tsx @@ -12,8 +12,10 @@ import { appendVideoToDomAndPlay, fetchVideo } from "fake-external-lib"; * * 1. See if you can fix the errors below by making the type of state more specific. */ +type State = "loading" | "loaded" | "error"; + export const useLoadAsyncVideo = (src: string) => { - const [state, setState] = useState("loading"); + const [state, setState] = useState("loading"); useEffect(() => { setState("loading"); diff --git a/src/06-advanced-hooks/48-discriminated-unions-in-usestate.problem.tsx b/src/06-advanced-hooks/48-discriminated-unions-in-usestate.problem.tsx index 9d36f1e..5876ff3 100644 --- a/src/06-advanced-hooks/48-discriminated-unions-in-usestate.problem.tsx +++ b/src/06-advanced-hooks/48-discriminated-unions-in-usestate.problem.tsx @@ -1,8 +1,17 @@ import { appendVideoToDomAndPlay, fetchVideo } from "fake-external-lib"; import { useEffect, useState } from "react"; +type Status = + | { + status: "error"; + error: Error; + } + | { + status: "loading" | "loaded"; + }; + export const useLoadAsyncVideo = (src: string) => { - const [state, setState] = useState({ + const [state, setState] = useState({ status: "loading", }); diff --git a/src/06-advanced-hooks/49-discriminated-tuples-from-custom-hooks.problem.tsx b/src/06-advanced-hooks/49-discriminated-tuples-from-custom-hooks.problem.tsx index 432baac..0936554 100644 --- a/src/06-advanced-hooks/49-discriminated-tuples-from-custom-hooks.problem.tsx +++ b/src/06-advanced-hooks/49-discriminated-tuples-from-custom-hooks.problem.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; -export type Result = [ - "loading" | "success" | "error", - T | Error | undefined, -]; +export type Result = + | ["loading", undefined] + | ["success", T] + | ["error", Error]; /** * Let's look at one more example of discriminated unions. This time, we're @@ -35,7 +35,7 @@ export const useData = (url: string): Result => { const Component = () => { const [status, value] = useData<{ title: string }>( - "https://jsonplaceholder.typicode.com/todos/1", + "https://jsonplaceholder.typicode.com/todos/1" ); if (status === "loading") { diff --git a/src/06-advanced-hooks/50-use-state-overloads.problem.ts b/src/06-advanced-hooks/50-use-state-overloads.problem.ts index 0265ade..9bd3c65 100644 --- a/src/06-advanced-hooks/50-use-state-overloads.problem.ts +++ b/src/06-advanced-hooks/50-use-state-overloads.problem.ts @@ -19,6 +19,8 @@ useState; * HINT - you'll need to use the function keyword THREE times. */ +function maybeReturnsString(defaultString: string): string; +function maybeReturnsString(): undefined | string; function maybeReturnsString(defaultString?: string) { // If you pass a string, it always returns a string if (defaultString) { @@ -34,5 +36,5 @@ const example2 = maybeReturnsString(); type tests = [ Expect>, - Expect>, + Expect> ]; diff --git a/src/06-advanced-hooks/51-function-overloads-in-hooks.problem.ts b/src/06-advanced-hooks/51-function-overloads-in-hooks.problem.ts index 7c137fc..7ef69e5 100644 --- a/src/06-advanced-hooks/51-function-overloads-in-hooks.problem.ts +++ b/src/06-advanced-hooks/51-function-overloads-in-hooks.problem.ts @@ -7,7 +7,14 @@ import { Equal, Expect } from "../helpers/type-utils"; * * If you pass a default value, it should NOT include undefined. */ -export function useStateAsObject(initial: T) { +type UseStateReturnValue = { + value: T; + set: React.Dispatch>; +}; + +export function useStateAsObject(): UseStateReturnValue; +export function useStateAsObject(initial: T): UseStateReturnValue; +export function useStateAsObject(initial?: T) { const [value, set] = useState(initial); return { @@ -28,7 +35,7 @@ type ExampleTests = [ typeof notUndefined.set, React.Dispatch> > - >, + > ]; /** @@ -43,5 +50,5 @@ type NumTests = [ typeof hasUndefined.set, React.Dispatch> > - >, + > ]; diff --git a/src/06-advanced-hooks/52-currying-hooks.problem.ts b/src/06-advanced-hooks/52-currying-hooks.problem.ts index ccce13c..a6de3c4 100644 --- a/src/06-advanced-hooks/52-currying-hooks.problem.ts +++ b/src/06-advanced-hooks/52-currying-hooks.problem.ts @@ -15,7 +15,10 @@ const useCustomState = (initial: TValue) => { * any type. We want to make sure that the type of the * thing returned is inferred properly. */ - useComputed: (factory: (value: any) => any, deps?: DependencyList) => { + useComputed: ( + factory: (value: TValue) => TResult, + deps?: DependencyList + ) => { return useMemo(() => { return factory(value); }, [value, ...(deps || [])]); @@ -32,7 +35,7 @@ const Component = () => { * useComputed? */ const reversedAsString = arrayOfNums.useComputed((nums) => - Array.from(nums).reverse().map(String), + Array.from(nums).reverse().map(String) ); type test = Expect>; diff --git a/src/07-types-deep-dive/54-understanding-jsx-element.problem.tsx b/src/07-types-deep-dive/54-understanding-jsx-element.problem.tsx index 6b8506f..ab5e64c 100644 --- a/src/07-types-deep-dive/54-understanding-jsx-element.problem.tsx +++ b/src/07-types-deep-dive/54-understanding-jsx-element.problem.tsx @@ -12,7 +12,7 @@ type ClickMeThree = React.ReactNode; /** * 2. What is the return type of this Component? */ -const Component = () => { +const Component = (): React.ReactNode => { return
Hello world
; }; @@ -48,6 +48,6 @@ const Component3 = (): React.ReactElement => { /** * 4b. ...but this one does? */ -const Component4 = (): React.ReactElement => { +const Component4 = (): ClickMeThree => { return "hello!"; }; diff --git a/src/07-types-deep-dive/55-strongly-typing-children.problem.tsx b/src/07-types-deep-dive/55-strongly-typing-children.problem.tsx index 1a89bd9..232d1c1 100644 --- a/src/07-types-deep-dive/55-strongly-typing-children.problem.tsx +++ b/src/07-types-deep-dive/55-strongly-typing-children.problem.tsx @@ -24,7 +24,7 @@ const Option = () => { return () as OptionType; }; -const Select = (props: { children: OptionType }) => { +const Select = (props: { children: React.ReactElement }) => { return ; }; diff --git a/src/07-types-deep-dive/58-appending-to-global-namespace.problem.ts b/src/07-types-deep-dive/58-appending-to-global-namespace.problem.ts index 761030b..d3f7a79 100644 --- a/src/07-types-deep-dive/58-appending-to-global-namespace.problem.ts +++ b/src/07-types-deep-dive/58-appending-to-global-namespace.problem.ts @@ -9,7 +9,11 @@ import { Equal, Expect } from "../helpers/type-utils"; */ declare global { - namespace React {} + namespace React { + interface MyInterface { + foo: string; + } + } } type test = Expect>; diff --git a/src/07-types-deep-dive/59-declaration-merging-in-global-namespace.problem.ts b/src/07-types-deep-dive/59-declaration-merging-in-global-namespace.problem.ts index 470f2a0..45690f6 100644 --- a/src/07-types-deep-dive/59-declaration-merging-in-global-namespace.problem.ts +++ b/src/07-types-deep-dive/59-declaration-merging-in-global-namespace.problem.ts @@ -5,6 +5,9 @@ declare global { interface MyAwesomeInterface { foo: string; } + interface MyAwesomeInterface { + bar: string; + } } } diff --git a/src/07-types-deep-dive/60-add-new-global-element.problem.tsx b/src/07-types-deep-dive/60-add-new-global-element.problem.tsx index a0b763d..7ad4f3e 100644 --- a/src/07-types-deep-dive/60-add-new-global-element.problem.tsx +++ b/src/07-types-deep-dive/60-add-new-global-element.problem.tsx @@ -10,6 +10,16 @@ * interface in the JSX namespace. */ +declare global { + namespace JSX { + interface IntrinsicElements { + something: { + id: string; + }; + } + } +} + <> diff --git a/src/07-types-deep-dive/62-add-attribute-to-all-elements.problem.tsx b/src/07-types-deep-dive/62-add-attribute-to-all-elements.problem.tsx index ae9b9f1..79841f5 100644 --- a/src/07-types-deep-dive/62-add-attribute-to-all-elements.problem.tsx +++ b/src/07-types-deep-dive/62-add-attribute-to-all-elements.problem.tsx @@ -6,6 +6,14 @@ * a new attribute to all React elements. */ +declare global { + namespace React { + interface HTMLAttributes { + testId: string; + } + } +} + <>
; }; diff --git a/src/08-advanced-patterns/67-hoc.problem.tsx b/src/08-advanced-patterns/67-hoc.problem.tsx index 70e4167..00c2565 100644 --- a/src/08-advanced-patterns/67-hoc.problem.tsx +++ b/src/08-advanced-patterns/67-hoc.problem.tsx @@ -1,9 +1,9 @@ import { Router, useRouter } from "fake-external-lib"; -export const withRouter = (Component: any) => { - const NewComponent = (props: any) => { +export const withRouter = (Component: React.FC) => { + const NewComponent = (props: Omit) => { const router = useRouter(); - return ; + return ; }; NewComponent.displayName = `withRouter(${Component.displayName})`; diff --git a/src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx b/src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx index 781037c..19ff5d4 100644 --- a/src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx +++ b/src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx @@ -1,13 +1,21 @@ import { Router, useRouter } from "fake-external-lib"; import { Equal, Expect } from "../helpers/type-utils"; -export const withRouter = (Component: React.ComponentType) => { +export const withRouter = ( + Component: (props: TProps) => React.ReactNode +): ((props: Omit) => React.ReactNode) => { const NewComponent = (props: Omit) => { const router = useRouter(); return ; }; - NewComponent.displayName = `withRouter(${Component.displayName})`; + NewComponent.displayName = `withRouter(${ + ( + Component as { + displayName?: string; + } + ).displayName + })`; return NewComponent; }; diff --git a/src/08-advanced-patterns/69-as-prop.problem.tsx b/src/08-advanced-patterns/69-as-prop.problem.tsx index e75a490..fa19c52 100644 --- a/src/08-advanced-patterns/69-as-prop.problem.tsx +++ b/src/08-advanced-patterns/69-as-prop.problem.tsx @@ -24,8 +24,12 @@ import { Equal, Expect } from "../helpers/type-utils"; -export const Wrapper = (props: any) => { - const Comp = props.as; +export const Wrapper = ( + props: { + as: TAs; + } & React.ComponentProps +) => { + const Comp = props.as as string; return ; }; diff --git a/src/08-advanced-patterns/69-as-prop.solution.2.tsx b/src/08-advanced-patterns/69-as-prop.solution.2.tsx index 089ae87..0a4faee 100644 --- a/src/08-advanced-patterns/69-as-prop.solution.2.tsx +++ b/src/08-advanced-patterns/69-as-prop.solution.2.tsx @@ -3,7 +3,7 @@ import { Equal, Expect } from "../helpers/type-utils"; export const Wrapper = ( props: { as: TAs; - } & React.ComponentProps, + } & React.ComponentProps ) => { const Comp = props.as as string; diff --git a/src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx b/src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx index fbd721f..04a126b 100644 --- a/src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx +++ b/src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { ElementType } from "react"; import { Equal, Expect } from "../helpers/type-utils"; /** @@ -17,10 +17,10 @@ import { Equal, Expect } from "../helpers/type-utils"; * - ComponentPropsWithRef * - ComponentProps */ -export const Wrapper = ( +export const Wrapper = ( props: { as: TAs; - } & React.ComponentProps, + } & React.ComponentProps ) => { const Comp = props.as as string; diff --git a/src/08-advanced-patterns/71-as-prop-with-default.problem.tsx b/src/08-advanced-patterns/71-as-prop-with-default.problem.tsx index 28b2e79..729f433 100644 --- a/src/08-advanced-patterns/71-as-prop-with-default.problem.tsx +++ b/src/08-advanced-patterns/71-as-prop-with-default.problem.tsx @@ -1,10 +1,10 @@ import { ElementType } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -export const Link = ( +export const Link = ( props: { - as: TAs; - } & React.ComponentPropsWithoutRef, + as?: TAs; + } & React.ComponentPropsWithoutRef ) => { const { as: Comp = "a", ...rest } = props; return ; @@ -66,7 +66,7 @@ const Example2 = () => { const Custom = ( props: { thisIsRequired: boolean }, - ref: React.ForwardedRef, + ref: React.ForwardedRef ) => { return ; }; diff --git a/src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx b/src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx index cc6591b..6d3fe05 100644 --- a/src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx +++ b/src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx @@ -1,4 +1,4 @@ -import { useForm } from "react-hook-form"; +import { DefaultValues, FieldValues, useForm } from "react-hook-form"; import { Equal, Expect, Extends } from "../helpers/type-utils"; /** @@ -12,9 +12,9 @@ import { Equal, Expect, Extends } from "../helpers/type-utils"; * * defaultValues as DefaultValues */ -const useCustomForm = (defaultValues: any) => { +const useCustomForm = (defaultValues: TValues) => { const form = useForm({ - defaultValues: defaultValues, + defaultValues: defaultValues as DefaultValues, }); return { @@ -31,7 +31,7 @@ useCustomForm(); useCustomForm( // @ts-expect-error defaultValues must be an object - 2, + 2 ); const customForm = useCustomForm({ diff --git a/src/09-external-libraries/75-react-select.problem.tsx b/src/09-external-libraries/75-react-select.problem.tsx index 77dbf9f..45319e1 100644 --- a/src/09-external-libraries/75-react-select.problem.tsx +++ b/src/09-external-libraries/75-react-select.problem.tsx @@ -1,4 +1,4 @@ -import ReactSelect from "react-select"; +import ReactSelect, { GroupBase, Props } from "react-select"; import { Equal, Expect } from "../helpers/type-utils"; /** @@ -7,7 +7,13 @@ import { Equal, Expect } from "../helpers/type-utils"; * * Here's a clue: ReactSelect exports a type called 'Props'... */ -export const Select = (props) => { +export const Select = < + Option = unknown, + IsMulti extends boolean = false, + Group extends GroupBase