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/63-lazy-load-component.problem.tsx b/src/08-advanced-patterns/63-lazy-load-component.problem.tsx
index 7fe3b5e..8f99c91 100644
--- a/src/08-advanced-patterns/63-lazy-load-component.problem.tsx
+++ b/src/08-advanced-patterns/63-lazy-load-component.problem.tsx
@@ -1,8 +1,10 @@
-import { lazy, Suspense, useMemo } from "react";
+import { ComponentProps, lazy, Suspense, useMemo } from "react";
-type Props = {
- loader: unknown;
-};
+type Props> = {
+ loader: () => Promise<{
+ default: T;
+ }>;
+} & ComponentProps;
/**
* 1. This component is supposed to take a loader function that returns a
@@ -16,7 +18,10 @@ type Props = {
* - You'll need to make this a generic component!
* - React.ComponentProps will come in handy, as will React.ComponentType
*/
-function LazyLoad({ loader, ...props }: Props) {
+function LazyLoad>({
+ loader,
+ ...props
+}: Props) {
const LazyComponent = useMemo(() => lazy(loader), [loader]);
return (
diff --git a/src/08-advanced-patterns/64-render-props.problem.tsx b/src/08-advanced-patterns/64-render-props.problem.tsx
index e31815f..3c38676 100644
--- a/src/08-advanced-patterns/64-render-props.problem.tsx
+++ b/src/08-advanced-patterns/64-render-props.problem.tsx
@@ -17,7 +17,7 @@ interface ModalChildProps {
closeModal: () => void;
}
-const Modal = ({ children }: any) => {
+const Modal = ({ children }: { children: React.FC }) => {
const [isOpen, setIsOpen] = useState(false);
return (
@@ -31,7 +31,7 @@ const Modal = ({ children }: any) => {
Modal
,
- document.getElementById("modal-root")!,
+ document.getElementById("modal-root")!
)}
>
);
diff --git a/src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx b/src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx
index 8a10719..0d852f2 100644
--- a/src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx
+++ b/src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx
@@ -2,6 +2,8 @@ import { Equal, Expect } from "../helpers/type-utils";
type InputProps = React.ComponentProps<"input">;
+type InputType = "text" | "number" | "password";
+
/**
* All these components take the same props!
*
@@ -12,7 +14,7 @@ type InputProps = React.ComponentProps<"input">;
*
* Hint: Record and satisfies will come in handy.
*/
-const COMPONENTS = {
+const COMPONENTS: Record> = {
text: (props) => {
return ;
},
@@ -24,7 +26,7 @@ const COMPONENTS = {
},
};
-export const Input = (props: unknown) => {
+export const Input = (props: { type: InputType } & InputProps) => {
const Component = COMPONENTS[props.type];
return ;
};
diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx
index c6931fb..5c3eefa 100644
--- a/src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx
+++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx
@@ -6,9 +6,10 @@ import { Equal, Expect } from "../helpers/type-utils";
* give fixedForwardRef a type signature that allows it to
* work with the example below.
*/
-function fixedForwardRef(
- render: (props: any, ref: any) => any,
-): (props: any) => any {
+
+function fixedForwardRef(
+ render: (props: P, ref: React.Ref) => React.ReactNode
+): (props: P & React.RefAttributes) => React.ReactNode {
return forwardRef(render) as any;
}
@@ -19,7 +20,7 @@ type Props = {
export const Table = (
props: Props,
- ref: ForwardedRef,
+ ref: ForwardedRef
) => {
return ;
};
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