From 10c28d6d82b5dafbc2cc8bdd19034571d25d1e49 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 24 Jul 2023 15:43:50 +0100 Subject: [PATCH 01/73] Removed const generics --- .../64-const-generics-in-generic-components.problem.tsx | 7 ------- ...ender-props.problem.tsx => 64-render-props.problem.tsx} | 0 ...der-props.solution.tsx => 64-render-props.solution.tsx} | 0 ....1.tsx => 65-forward-ref-with-generics.explainer.1.tsx} | 0 ....2.tsx => 65-forward-ref-with-generics.explainer.2.tsx} | 0 ...er.3.ts => 65-forward-ref-with-generics.explainer.3.ts} | 0 ...em.tsx => 66-forward-ref-as-local-function.problem.tsx} | 0 ...tsx => 66-forward-ref-as-local-function.solution.1.tsx} | 0 ...tsx => 66-forward-ref-as-local-function.solution.2.tsx} | 0 .../{68-hoc.problem.tsx => 67-hoc.problem.tsx} | 0 .../{68-hoc.solution.tsx => 67-hoc.solution.tsx} | 0 ...=> 68-record-of-components-with-same-props.problem.tsx} | 0 ...68-record-of-components-with-same-props.solution.1.tsx} | 0 ...68-record-of-components-with-same-props.solution.2.tsx} | 0 .../{70-as-prop.problem.tsx => 69-as-prop.problem.tsx} | 0 ...70-as-prop.solution.1.tsx => 69-as-prop.solution.1.tsx} | 0 ...70-as-prop.solution.2.tsx => 69-as-prop.solution.2.tsx} | 0 ...m.tsx => 70-as-prop-with-custom-components.problem.tsx} | 0 ....tsx => 70-as-prop-with-custom-components.solution.tsx} | 0 ...ult.problem.tsx => 71-as-prop-with-default.problem.tsx} | 0 ...lution.1.tsx => 71-as-prop-with-default.solution.1.tsx} | 0 ...lution.2.tsx => 71-as-prop-with-default.solution.2.tsx} | 0 ...problem.tsx => 72-as-prop-with-forward-ref.problem.tsx} | 0 ...lution.tsx => 72-as-prop-with-forward-ref.solution.tsx} | 0 ...form.explainer.tsx => 73-react-hook-form.explainer.tsx} | 0 ....problem.tsx => 74-react-hook-form-wrapper.problem.tsx} | 0 ...olution.tsx => 74-react-hook-form-wrapper.solution.tsx} | 0 ...eact-select.problem.tsx => 75-react-select.problem.tsx} | 0 ...ct-select.solution.tsx => 75-react-select.solution.tsx} | 0 ...eact-query.explainer.ts => 76-react-query.explainer.ts} | 0 ...rapper.problem.ts => 77-react-query-wrapper.problem.ts} | 0 ...pper.solution.ts => 77-react-query-wrapper.solution.ts} | 0 32 files changed, 7 deletions(-) delete mode 100644 src/08-advanced-patterns/64-const-generics-in-generic-components.problem.tsx rename src/08-advanced-patterns/{65-render-props.problem.tsx => 64-render-props.problem.tsx} (100%) rename src/08-advanced-patterns/{65-render-props.solution.tsx => 64-render-props.solution.tsx} (100%) rename src/08-advanced-patterns/{66-forward-ref-with-generics.explainer.1.tsx => 65-forward-ref-with-generics.explainer.1.tsx} (100%) rename src/08-advanced-patterns/{66-forward-ref-with-generics.explainer.2.tsx => 65-forward-ref-with-generics.explainer.2.tsx} (100%) rename src/08-advanced-patterns/{66-forward-ref-with-generics.explainer.3.ts => 65-forward-ref-with-generics.explainer.3.ts} (100%) rename src/08-advanced-patterns/{67-forward-ref-as-local-function.problem.tsx => 66-forward-ref-as-local-function.problem.tsx} (100%) rename src/08-advanced-patterns/{67-forward-ref-as-local-function.solution.1.tsx => 66-forward-ref-as-local-function.solution.1.tsx} (100%) rename src/08-advanced-patterns/{67-forward-ref-as-local-function.solution.2.tsx => 66-forward-ref-as-local-function.solution.2.tsx} (100%) rename src/08-advanced-patterns/{68-hoc.problem.tsx => 67-hoc.problem.tsx} (100%) rename src/08-advanced-patterns/{68-hoc.solution.tsx => 67-hoc.solution.tsx} (100%) rename src/08-advanced-patterns/{69-record-of-components-with-same-props.problem.tsx => 68-record-of-components-with-same-props.problem.tsx} (100%) rename src/08-advanced-patterns/{69-record-of-components-with-same-props.solution.1.tsx => 68-record-of-components-with-same-props.solution.1.tsx} (100%) rename src/08-advanced-patterns/{69-record-of-components-with-same-props.solution.2.tsx => 68-record-of-components-with-same-props.solution.2.tsx} (100%) rename src/08-advanced-patterns/{70-as-prop.problem.tsx => 69-as-prop.problem.tsx} (100%) rename src/08-advanced-patterns/{70-as-prop.solution.1.tsx => 69-as-prop.solution.1.tsx} (100%) rename src/08-advanced-patterns/{70-as-prop.solution.2.tsx => 69-as-prop.solution.2.tsx} (100%) rename src/08-advanced-patterns/{71-as-prop-with-custom-components.problem.tsx => 70-as-prop-with-custom-components.problem.tsx} (100%) rename src/08-advanced-patterns/{71-as-prop-with-custom-components.solution.tsx => 70-as-prop-with-custom-components.solution.tsx} (100%) rename src/08-advanced-patterns/{72-as-prop-with-default.problem.tsx => 71-as-prop-with-default.problem.tsx} (100%) rename src/08-advanced-patterns/{72-as-prop-with-default.solution.1.tsx => 71-as-prop-with-default.solution.1.tsx} (100%) rename src/08-advanced-patterns/{72-as-prop-with-default.solution.2.tsx => 71-as-prop-with-default.solution.2.tsx} (100%) rename src/08-advanced-patterns/{73-as-prop-with-forward-ref.problem.tsx => 72-as-prop-with-forward-ref.problem.tsx} (100%) rename src/08-advanced-patterns/{73-as-prop-with-forward-ref.solution.tsx => 72-as-prop-with-forward-ref.solution.tsx} (100%) rename src/09-external-libraries/{74-react-hook-form.explainer.tsx => 73-react-hook-form.explainer.tsx} (100%) rename src/09-external-libraries/{75-react-hook-form-wrapper.problem.tsx => 74-react-hook-form-wrapper.problem.tsx} (100%) rename src/09-external-libraries/{75-react-hook-form-wrapper.solution.tsx => 74-react-hook-form-wrapper.solution.tsx} (100%) rename src/09-external-libraries/{76-react-select.problem.tsx => 75-react-select.problem.tsx} (100%) rename src/09-external-libraries/{76-react-select.solution.tsx => 75-react-select.solution.tsx} (100%) rename src/09-external-libraries/{77-react-query.explainer.ts => 76-react-query.explainer.ts} (100%) rename src/09-external-libraries/{78-react-query-wrapper.problem.ts => 77-react-query-wrapper.problem.ts} (100%) rename src/09-external-libraries/{78-react-query-wrapper.solution.ts => 77-react-query-wrapper.solution.ts} (100%) diff --git a/src/08-advanced-patterns/64-const-generics-in-generic-components.problem.tsx b/src/08-advanced-patterns/64-const-generics-in-generic-components.problem.tsx deleted file mode 100644 index 994484f..0000000 --- a/src/08-advanced-patterns/64-const-generics-in-generic-components.problem.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Have a Form component that can receive an object where - * the keys are the different form elements, and have the - * values inferred based on values passed in. - */ - -const Form = >() => {}; diff --git a/src/08-advanced-patterns/65-render-props.problem.tsx b/src/08-advanced-patterns/64-render-props.problem.tsx similarity index 100% rename from src/08-advanced-patterns/65-render-props.problem.tsx rename to src/08-advanced-patterns/64-render-props.problem.tsx diff --git a/src/08-advanced-patterns/65-render-props.solution.tsx b/src/08-advanced-patterns/64-render-props.solution.tsx similarity index 100% rename from src/08-advanced-patterns/65-render-props.solution.tsx rename to src/08-advanced-patterns/64-render-props.solution.tsx diff --git a/src/08-advanced-patterns/66-forward-ref-with-generics.explainer.1.tsx b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx similarity index 100% rename from src/08-advanced-patterns/66-forward-ref-with-generics.explainer.1.tsx rename to src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx diff --git a/src/08-advanced-patterns/66-forward-ref-with-generics.explainer.2.tsx b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.2.tsx similarity index 100% rename from src/08-advanced-patterns/66-forward-ref-with-generics.explainer.2.tsx rename to src/08-advanced-patterns/65-forward-ref-with-generics.explainer.2.tsx diff --git a/src/08-advanced-patterns/66-forward-ref-with-generics.explainer.3.ts b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts similarity index 100% rename from src/08-advanced-patterns/66-forward-ref-with-generics.explainer.3.ts rename to src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts diff --git a/src/08-advanced-patterns/67-forward-ref-as-local-function.problem.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx similarity index 100% rename from src/08-advanced-patterns/67-forward-ref-as-local-function.problem.tsx rename to src/08-advanced-patterns/66-forward-ref-as-local-function.problem.tsx diff --git a/src/08-advanced-patterns/67-forward-ref-as-local-function.solution.1.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx similarity index 100% rename from src/08-advanced-patterns/67-forward-ref-as-local-function.solution.1.tsx rename to src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx diff --git a/src/08-advanced-patterns/67-forward-ref-as-local-function.solution.2.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx similarity index 100% rename from src/08-advanced-patterns/67-forward-ref-as-local-function.solution.2.tsx rename to src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx diff --git a/src/08-advanced-patterns/68-hoc.problem.tsx b/src/08-advanced-patterns/67-hoc.problem.tsx similarity index 100% rename from src/08-advanced-patterns/68-hoc.problem.tsx rename to src/08-advanced-patterns/67-hoc.problem.tsx diff --git a/src/08-advanced-patterns/68-hoc.solution.tsx b/src/08-advanced-patterns/67-hoc.solution.tsx similarity index 100% rename from src/08-advanced-patterns/68-hoc.solution.tsx rename to src/08-advanced-patterns/67-hoc.solution.tsx diff --git a/src/08-advanced-patterns/69-record-of-components-with-same-props.problem.tsx b/src/08-advanced-patterns/68-record-of-components-with-same-props.problem.tsx similarity index 100% rename from src/08-advanced-patterns/69-record-of-components-with-same-props.problem.tsx rename to src/08-advanced-patterns/68-record-of-components-with-same-props.problem.tsx diff --git a/src/08-advanced-patterns/69-record-of-components-with-same-props.solution.1.tsx b/src/08-advanced-patterns/68-record-of-components-with-same-props.solution.1.tsx similarity index 100% rename from src/08-advanced-patterns/69-record-of-components-with-same-props.solution.1.tsx rename to src/08-advanced-patterns/68-record-of-components-with-same-props.solution.1.tsx diff --git a/src/08-advanced-patterns/69-record-of-components-with-same-props.solution.2.tsx b/src/08-advanced-patterns/68-record-of-components-with-same-props.solution.2.tsx similarity index 100% rename from src/08-advanced-patterns/69-record-of-components-with-same-props.solution.2.tsx rename to src/08-advanced-patterns/68-record-of-components-with-same-props.solution.2.tsx diff --git a/src/08-advanced-patterns/70-as-prop.problem.tsx b/src/08-advanced-patterns/69-as-prop.problem.tsx similarity index 100% rename from src/08-advanced-patterns/70-as-prop.problem.tsx rename to src/08-advanced-patterns/69-as-prop.problem.tsx diff --git a/src/08-advanced-patterns/70-as-prop.solution.1.tsx b/src/08-advanced-patterns/69-as-prop.solution.1.tsx similarity index 100% rename from src/08-advanced-patterns/70-as-prop.solution.1.tsx rename to src/08-advanced-patterns/69-as-prop.solution.1.tsx diff --git a/src/08-advanced-patterns/70-as-prop.solution.2.tsx b/src/08-advanced-patterns/69-as-prop.solution.2.tsx similarity index 100% rename from src/08-advanced-patterns/70-as-prop.solution.2.tsx rename to src/08-advanced-patterns/69-as-prop.solution.2.tsx diff --git a/src/08-advanced-patterns/71-as-prop-with-custom-components.problem.tsx b/src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx similarity index 100% rename from src/08-advanced-patterns/71-as-prop-with-custom-components.problem.tsx rename to src/08-advanced-patterns/70-as-prop-with-custom-components.problem.tsx diff --git a/src/08-advanced-patterns/71-as-prop-with-custom-components.solution.tsx b/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx similarity index 100% rename from src/08-advanced-patterns/71-as-prop-with-custom-components.solution.tsx rename to src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx diff --git a/src/08-advanced-patterns/72-as-prop-with-default.problem.tsx b/src/08-advanced-patterns/71-as-prop-with-default.problem.tsx similarity index 100% rename from src/08-advanced-patterns/72-as-prop-with-default.problem.tsx rename to src/08-advanced-patterns/71-as-prop-with-default.problem.tsx diff --git a/src/08-advanced-patterns/72-as-prop-with-default.solution.1.tsx b/src/08-advanced-patterns/71-as-prop-with-default.solution.1.tsx similarity index 100% rename from src/08-advanced-patterns/72-as-prop-with-default.solution.1.tsx rename to src/08-advanced-patterns/71-as-prop-with-default.solution.1.tsx diff --git a/src/08-advanced-patterns/72-as-prop-with-default.solution.2.tsx b/src/08-advanced-patterns/71-as-prop-with-default.solution.2.tsx similarity index 100% rename from src/08-advanced-patterns/72-as-prop-with-default.solution.2.tsx rename to src/08-advanced-patterns/71-as-prop-with-default.solution.2.tsx diff --git a/src/08-advanced-patterns/73-as-prop-with-forward-ref.problem.tsx b/src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx similarity index 100% rename from src/08-advanced-patterns/73-as-prop-with-forward-ref.problem.tsx rename to src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx diff --git a/src/08-advanced-patterns/73-as-prop-with-forward-ref.solution.tsx b/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx similarity index 100% rename from src/08-advanced-patterns/73-as-prop-with-forward-ref.solution.tsx rename to src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx diff --git a/src/09-external-libraries/74-react-hook-form.explainer.tsx b/src/09-external-libraries/73-react-hook-form.explainer.tsx similarity index 100% rename from src/09-external-libraries/74-react-hook-form.explainer.tsx rename to src/09-external-libraries/73-react-hook-form.explainer.tsx diff --git a/src/09-external-libraries/75-react-hook-form-wrapper.problem.tsx b/src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx similarity index 100% rename from src/09-external-libraries/75-react-hook-form-wrapper.problem.tsx rename to src/09-external-libraries/74-react-hook-form-wrapper.problem.tsx diff --git a/src/09-external-libraries/75-react-hook-form-wrapper.solution.tsx b/src/09-external-libraries/74-react-hook-form-wrapper.solution.tsx similarity index 100% rename from src/09-external-libraries/75-react-hook-form-wrapper.solution.tsx rename to src/09-external-libraries/74-react-hook-form-wrapper.solution.tsx diff --git a/src/09-external-libraries/76-react-select.problem.tsx b/src/09-external-libraries/75-react-select.problem.tsx similarity index 100% rename from src/09-external-libraries/76-react-select.problem.tsx rename to src/09-external-libraries/75-react-select.problem.tsx diff --git a/src/09-external-libraries/76-react-select.solution.tsx b/src/09-external-libraries/75-react-select.solution.tsx similarity index 100% rename from src/09-external-libraries/76-react-select.solution.tsx rename to src/09-external-libraries/75-react-select.solution.tsx diff --git a/src/09-external-libraries/77-react-query.explainer.ts b/src/09-external-libraries/76-react-query.explainer.ts similarity index 100% rename from src/09-external-libraries/77-react-query.explainer.ts rename to src/09-external-libraries/76-react-query.explainer.ts diff --git a/src/09-external-libraries/78-react-query-wrapper.problem.ts b/src/09-external-libraries/77-react-query-wrapper.problem.ts similarity index 100% rename from src/09-external-libraries/78-react-query-wrapper.problem.ts rename to src/09-external-libraries/77-react-query-wrapper.problem.ts diff --git a/src/09-external-libraries/78-react-query-wrapper.solution.ts b/src/09-external-libraries/77-react-query-wrapper.solution.ts similarity index 100% rename from src/09-external-libraries/78-react-query-wrapper.solution.ts rename to src/09-external-libraries/77-react-query-wrapper.solution.ts From 59cbbfd03451375b2f94cb2ad4e39f488388beab Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 24 Jul 2023 15:51:28 +0100 Subject: [PATCH 02/73] Finished 64 --- src/08-advanced-patterns/64-render-props.solution.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/08-advanced-patterns/64-render-props.solution.tsx b/src/08-advanced-patterns/64-render-props.solution.tsx index 8b3ffc1..f88116a 100644 --- a/src/08-advanced-patterns/64-render-props.solution.tsx +++ b/src/08-advanced-patterns/64-render-props.solution.tsx @@ -8,11 +8,7 @@ interface ModalChildProps { closeModal: () => void; } -const Modal = ({ - children, -}: { - children: (props: ModalChildProps) => React.ReactNode; -}) => { +const Modal = ({ children }: { children: React.FC }) => { const [isOpen, setIsOpen] = useState(false); return ( From aa556e7aae837cde2a8c04e772dda1a624c2284a Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 24 Jul 2023 15:54:11 +0100 Subject: [PATCH 03/73] Added more tests to forwardRef explainer --- ...-forward-ref-with-generics.explainer.1.tsx | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx index 8f96727..a0a1042 100644 --- a/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx +++ b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, useRef } from "react"; import { Equal, Expect } from "../helpers/type-utils"; type Props = { @@ -31,20 +31,36 @@ type Props = { * By doing it this way, we preserve the generic context of the function * being passed in. */ -export const Table = (props: Props, ref: ForwardedRef) => { - return null; +export const Table = ( + props: Props, + ref: ForwardedRef, +) => { + return ; }; const ForwardReffedTable = forwardRef(Table); const Parent = () => { + const tableRef = useRef(null); + const wrongRef = useRef(null); return ( - { - type test = Expect>; - return
123
; - }} - >
+ <> + { + type test = Expect>; + return
123
; + }} + /> + { + return
123
; + }} + /> + ); }; From 173aad5883a4a2c3c63ee3f9f04e15b860870954 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 24 Jul 2023 16:32:40 +0100 Subject: [PATCH 04/73] Fixed 66 example --- ...-forward-ref-as-local-function.problem.tsx | 36 +++++++++++++------ ...rward-ref-as-local-function.solution.1.tsx | 36 +++++++++++++------ ...rward-ref-as-local-function.solution.2.tsx | 34 +++++++++++++----- 3 files changed, 77 insertions(+), 29 deletions(-) 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 e78bbaf..c6931fb 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 @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, useRef } from "react"; import { Equal, Expect } from "../helpers/type-utils"; /** @@ -17,20 +17,36 @@ type Props = { renderRow: (item: T) => React.ReactNode; }; -export const Table = (props: Props, ref: ForwardedRef) => { - return null; +export const Table = ( + props: Props, + ref: ForwardedRef, +) => { + return
; }; const ForwardReffedTable = fixedForwardRef(Table); const Parent = () => { + const tableRef = useRef(null); + const wrongRef = useRef(null); return ( - { - type test = Expect>; - return
123
; - }} - >
+ <> + { + type test = Expect>; + return
123
; + }} + /> + { + return
123
; + }} + /> + ); }; diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx index 9c4652c..9a7a82d 100644 --- a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx +++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.1.tsx @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, useRef } from "react"; import { Equal, Expect } from "../helpers/type-utils"; function fixedForwardRef( @@ -12,20 +12,36 @@ type Props = { renderRow: (item: T) => React.ReactNode; }; -export const Table = (props: Props, ref: ForwardedRef) => { - return null; +export const Table = ( + props: Props, + ref: ForwardedRef, +) => { + return
; }; const ForwardReffedTable = fixedForwardRef(Table); const Parent = () => { + const tableRef = useRef(null); + const wrongRef = useRef(null); return ( - { - type test = Expect>; - return
123
; - }} - >
+ <> + { + type test = Expect>; + return
123
; + }} + /> + { + return
123
; + }} + /> + ); }; diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx index 37ae3f0..3678aec 100644 --- a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx +++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx @@ -13,20 +13,36 @@ type Props = { renderRow: (item: T) => React.ReactNode; }; -export const Table = (props: Props, ref: ForwardedRef) => { - return null; +export const Table = ( + props: Props, + ref: ForwardedRef, +) => { + return
; }; const ForwardReffedTable = fixedForwardRef(Table); const Parent = () => { + const tableRef = useRef(null); + const wrongRef = useRef(null); return ( - { - type test = Expect>; - return
123
; - }} - >
+ <> + { + type test = Expect>; + return
123
; + }} + /> + { + return
123
; + }} + /> + ); }; From 6b7e99d6cb2776aeffd222adb37891ecf41f6d63 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 24 Jul 2023 16:37:43 +0100 Subject: [PATCH 05/73] Fixed 66 --- .../66-forward-ref-as-local-function.solution.2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx index 3678aec..785c13c 100644 --- a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx +++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx @@ -1,6 +1,6 @@ import { Equal, Expect } from "../helpers/type-utils"; -import { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, useRef } from "react"; type FixedForwardRef = ( render: (props: P, ref: React.Ref) => React.ReactNode, From 2fd10e76bfd520b8e6959853f6a7e885c3694f00 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 10:39:16 +0100 Subject: [PATCH 06/73] Removed ElementRef from 66 --- .../66-forward-ref-as-local-function.solution.2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx index 785c13c..9a25ebc 100644 --- a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx +++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx @@ -1,6 +1,6 @@ import { Equal, Expect } from "../helpers/type-utils"; -import { ForwardedRef, forwardRef, useRef } from "react"; +import { ElementRef, ForwardedRef, forwardRef, useRef } from "react"; type FixedForwardRef = ( render: (props: P, ref: React.Ref) => React.ReactNode, From a080f2404d7624b1e9c3bc88d4cd946f344781f9 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 10:39:51 +0100 Subject: [PATCH 07/73] Removed ElementRef --- .../66-forward-ref-as-local-function.solution.2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx index 9a25ebc..785c13c 100644 --- a/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx +++ b/src/08-advanced-patterns/66-forward-ref-as-local-function.solution.2.tsx @@ -1,6 +1,6 @@ import { Equal, Expect } from "../helpers/type-utils"; -import { ElementRef, ForwardedRef, forwardRef, useRef } from "react"; +import { ForwardedRef, forwardRef, useRef } from "react"; type FixedForwardRef = ( render: (props: P, ref: React.Ref) => React.ReactNode, From 50acd9be41ccf4bb1faf2c2e2151717cb83a33ab Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 10:46:01 +0100 Subject: [PATCH 08/73] Improved forwardRef with generics explainer --- ...5-forward-ref-with-generics.explainer.3.ts | 63 ------------------- ...-forward-ref-with-generics.explainer.3.tsx | 46 ++++++++++++++ 2 files changed, 46 insertions(+), 63 deletions(-) delete mode 100644 src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts create mode 100644 src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.tsx diff --git a/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts deleted file mode 100644 index 873f796..0000000 --- a/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Equal, Expect } from "../helpers/type-utils"; - -/** - * Here's a detailed breakdown on why forwardRef doesn't work. - */ - -/** - * 1. We create a type that represents a function, but with - * some other attributes. - */ -type FuncExpected = { - (arg: Argument): Argument; - someOtherThing?: string; -}; - -/** - * 2. We create a function that takes a function as an argument, - * and infers the position of Argument. - * - * This function doesn't do anything at runtime - it just returns - * the function that was passed in. But it behaves similarly to - * forwardRef. - */ -const forwardRefShim = (func: FuncExpected) => { - return (arg: Argument) => func(arg); -}; - -/** - * 3. We create an identity function, that just takes in an argument - * and returns it. - */ -const identityFunc = (arg: Argument) => { - return arg; -}; - -/** - * 4. As you can see, when it's not wrapped, identityFunc returns - * the type that we pass in, 123. - */ -const result1 = identityFunc(123); - -type test1 = Expect>; - -/** - * 5. But when we wrap it in forwardRefShim, it loses its powers - * of inference! Just like forwardRef. - */ -const wrappedIdentityFunc = forwardRefShim(identityFunc); - -const result2 = wrappedIdentityFunc(123); - -type test2 = Expect>; - -/** - * 6. Here's the really crazy part. Go back up to FuncExpected. - * Comment out the someOtherThing property. - * - * It now works! This is because when a function is _just_ a function, - * TypeScript uses its higher-order function powers on it. But when - * it has other properties, it doesn't. - * - * Bizarre! - */ diff --git a/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.tsx b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.tsx new file mode 100644 index 0000000..40fb1ed --- /dev/null +++ b/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.3.tsx @@ -0,0 +1,46 @@ +import { Equal, Expect } from "../helpers/type-utils"; + +type TableProps = { + data: T[]; + renderRow: (item: T) => React.ReactNode; +}; + +export const Table = (props: TableProps) => { + return null; +}; + +type FC = { + (arg: Props): React.ReactNode; + // Try uncommenting this - and it works! + someOtherThing?: string; +}; + +const removeInference = (component: FC) => { + return component; +}; + +const TableWithoutInference = removeInference(Table); + +const Parent = () => { + return ( + <> + { + // Without inference, this is 'unknown' + type test = Expect>; + return
123
; + }} + >
+ +
{ + // With inference, it's 'string' + type test = Expect>; + return
123
; + }} + >
+ + ); +}; From ed9bf17f188be0473213beb3054721868d60f1eb Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 13:26:11 +0100 Subject: [PATCH 09/73] Changed HOC problem --- src/08-advanced-patterns/67-hoc.problem.tsx | 15 ----- ...7.5-hoc-for-generic-components.problem.tsx | 54 ++++++++++++++++++ ....5-hoc-for-generic-components.solution.tsx | 56 +++++++++++++++++++ 3 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx create mode 100644 src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx diff --git a/src/08-advanced-patterns/67-hoc.problem.tsx b/src/08-advanced-patterns/67-hoc.problem.tsx index fa071f1..70e4167 100644 --- a/src/08-advanced-patterns/67-hoc.problem.tsx +++ b/src/08-advanced-patterns/67-hoc.problem.tsx @@ -1,20 +1,5 @@ import { Router, useRouter } from "fake-external-lib"; -/** - * A higher-order component is a function that takes a component and returns a - * new component, with some additional props/behavior. - * - * In this case, we want to take a component that doesn't have a router prop, - * and add one. - * - * 1. Figure out the correct typings for the `withRouter` function. You'll - * need to use: - * - * - Generics - * - Omit - * - React.ComponentType - * - Probably an 'as' at least once - */ export const withRouter = (Component: any) => { const NewComponent = (props: any) => { const router = useRouter(); 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 new file mode 100644 index 0000000..18a2a4e --- /dev/null +++ b/src/08-advanced-patterns/67.5-hoc-for-generic-components.problem.tsx @@ -0,0 +1,54 @@ +import { Router, useRouter } from "fake-external-lib"; +import { Equal, Expect } from "../helpers/type-utils"; + +export const withRouter = ( + Component: React.FC, +) => { + const NewComponent = (props: Omit) => { + const router = useRouter(); + return ; + }; + + NewComponent.displayName = `withRouter(${Component.displayName})`; + + return NewComponent; +}; + +type TableProps = { + data: T[]; + renderRow: (item: T) => React.ReactNode; + router: Router; +}; + +export const Table = (props: TableProps) => { + return ; +}; + +const WrappedTable = withRouter(Table); + +<> + {/* @ts-expect-error router is required! */} +
{ + type test = Expect>; + return ; + }} + /> + + { + type test = Expect>; + return ; + }} + /> + + { + type test = Expect>; + return ; + }} + /> +; diff --git a/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx b/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx new file mode 100644 index 0000000..bb324d4 --- /dev/null +++ b/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx @@ -0,0 +1,56 @@ +import { Router, useRouter } from "fake-external-lib"; +import { Equal, Expect } from "../helpers/type-utils"; + +export const withRouter = ( + Component: (props: TProps) => React.ReactNode, +): ((props: Omit) => React.ReactNode) => { + const NewComponent = (props: Omit) => { + const router = useRouter(); + return ; + }; + + NewComponent.displayName = `withRouter(${ + (Component as { displayName?: string }).displayName + })`; + + return NewComponent; +}; + +type TableProps = { + data: T[]; + renderRow: (item: T) => React.ReactNode; + router: Router; +}; + +export const Table = (props: TableProps) => { + return
; +}; + +const WrappedTable = withRouter(Table); + +<> + {/* @ts-expect-error router is required! */} +
{ + type test = Expect>; + return ; + }} + /> + + { + type test = Expect>; + return ; + }} + /> + + { + type test = Expect>; + return ; + }} + /> +; From bb55b6b205e5d08bb4558c806b84625d6edfde90 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 13:33:43 +0100 Subject: [PATCH 10/73] Finished HOC --- src/08-advanced-patterns/67-hoc.solution.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/08-advanced-patterns/67-hoc.solution.tsx b/src/08-advanced-patterns/67-hoc.solution.tsx index 2cb7942..4a3dc0a 100644 --- a/src/08-advanced-patterns/67-hoc.solution.tsx +++ b/src/08-advanced-patterns/67-hoc.solution.tsx @@ -1,8 +1,6 @@ import { Router, useRouter } from "fake-external-lib"; -export const withRouter = ( - Component: React.FC, -) => { +export const withRouter = (Component: React.ComponentType) => { const NewComponent = (props: Omit) => { const router = useRouter(); return ; From 0b4eee4371425002d707cc429e199e0299dc8192 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 13:36:30 +0100 Subject: [PATCH 11/73] Fixed 67.5 --- .../67.5-hoc-for-generic-components.problem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 18a2a4e..781037c 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,9 +1,7 @@ import { Router, useRouter } from "fake-external-lib"; import { Equal, Expect } from "../helpers/type-utils"; -export const withRouter = ( - Component: React.FC, -) => { +export const withRouter = (Component: React.ComponentType) => { const NewComponent = (props: Omit) => { const router = useRouter(); return ; From 7a6ad76a85ddc078a7f8669d2235588fac97393b Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 13:49:44 +0100 Subject: [PATCH 12/73] Completed 67.5 --- .../67.5-hoc-for-generic-components.solution.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx b/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx index bb324d4..ffd22d9 100644 --- a/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx +++ b/src/08-advanced-patterns/67.5-hoc-for-generic-components.solution.tsx @@ -1,7 +1,7 @@ import { Router, useRouter } from "fake-external-lib"; import { Equal, Expect } from "../helpers/type-utils"; -export const withRouter = ( +export const withRouter = ( Component: (props: TProps) => React.ReactNode, ): ((props: Omit) => React.ReactNode) => { const NewComponent = (props: Omit) => { @@ -10,7 +10,11 @@ export const withRouter = ( }; NewComponent.displayName = `withRouter(${ - (Component as { displayName?: string }).displayName + ( + Component as { + displayName?: string; + } + ).displayName })`; return NewComponent; From b460e6a27f80de451c6f033b405a046f0bc52c03 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 13:50:39 +0100 Subject: [PATCH 13/73] Changed 68 to 64.5 --- ....tsx => 64.5-record-of-components-with-same-props.problem.tsx} | 0 ...x => 64.5-record-of-components-with-same-props.solution.1.tsx} | 0 ...x => 64.5-record-of-components-with-same-props.solution.2.tsx} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/08-advanced-patterns/{68-record-of-components-with-same-props.problem.tsx => 64.5-record-of-components-with-same-props.problem.tsx} (100%) rename src/08-advanced-patterns/{68-record-of-components-with-same-props.solution.1.tsx => 64.5-record-of-components-with-same-props.solution.1.tsx} (100%) rename src/08-advanced-patterns/{68-record-of-components-with-same-props.solution.2.tsx => 64.5-record-of-components-with-same-props.solution.2.tsx} (100%) diff --git a/src/08-advanced-patterns/68-record-of-components-with-same-props.problem.tsx b/src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx similarity index 100% rename from src/08-advanced-patterns/68-record-of-components-with-same-props.problem.tsx rename to src/08-advanced-patterns/64.5-record-of-components-with-same-props.problem.tsx diff --git a/src/08-advanced-patterns/68-record-of-components-with-same-props.solution.1.tsx b/src/08-advanced-patterns/64.5-record-of-components-with-same-props.solution.1.tsx similarity index 100% rename from src/08-advanced-patterns/68-record-of-components-with-same-props.solution.1.tsx rename to src/08-advanced-patterns/64.5-record-of-components-with-same-props.solution.1.tsx diff --git a/src/08-advanced-patterns/68-record-of-components-with-same-props.solution.2.tsx b/src/08-advanced-patterns/64.5-record-of-components-with-same-props.solution.2.tsx similarity index 100% rename from src/08-advanced-patterns/68-record-of-components-with-same-props.solution.2.tsx rename to src/08-advanced-patterns/64.5-record-of-components-with-same-props.solution.2.tsx From 2fab9b0ff08daca5a51944c760b812dee4bff744 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 16:33:53 +0100 Subject: [PATCH 14/73] Fixed 69 --- src/08-advanced-patterns/69-as-prop.solution.1.tsx | 13 ++++++++----- src/08-advanced-patterns/69-as-prop.solution.2.tsx | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/08-advanced-patterns/69-as-prop.solution.1.tsx b/src/08-advanced-patterns/69-as-prop.solution.1.tsx index 939c576..3d7d67a 100644 --- a/src/08-advanced-patterns/69-as-prop.solution.1.tsx +++ b/src/08-advanced-patterns/69-as-prop.solution.1.tsx @@ -1,10 +1,13 @@ import { Equal, Expect } from "../helpers/type-utils"; -type AsProps = { - [K in keyof JSX.IntrinsicElements]: { - as: K; - } & JSX.IntrinsicElements[K]; -}[keyof JSX.IntrinsicElements]; +// Commented out because it makes the whole repo slow to typecheck +// Uncomment to see it working! + +// type AsProps = { +// [K in keyof JSX.IntrinsicElements]: { +// as: K; +// } & JSX.IntrinsicElements[K]; +// }[keyof JSX.IntrinsicElements]; export const Wrapper = (props: AsProps) => { const Comp = props.as; 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 d2729ad..089ae87 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; - } & JSX.IntrinsicElements[TAs], + } & React.ComponentProps, ) => { const Comp = props.as as string; From 76f2a1d3b4fe4df3bd336869554579363732b1d4 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 16:37:50 +0100 Subject: [PATCH 15/73] Fixed custom component --- .../70-as-prop-with-custom-components.problem.tsx | 7 ++----- .../70-as-prop-with-custom-components.solution.tsx | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) 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 dc2c983..197b258 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 @@ -45,11 +45,8 @@ const Example1 = () => { * Should work with Custom components! */ -const Custom = ( - props: { thisIsRequired: boolean }, - ref: React.ForwardedRef, -) => { - return ; +const Custom = (props: { thisIsRequired: boolean }) => { + return ; }; const Example2 = () => { diff --git a/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx b/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx index be6635a..95dc6ec 100644 --- a/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx +++ b/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx @@ -40,11 +40,8 @@ const Example1 = () => { * Should work with Custom components! */ -const Custom = ( - props: { thisIsRequired: boolean }, - ref: React.ForwardedRef, -) => { - return ; +const Custom = (props: { thisIsRequired: boolean }) => { + return ; }; const Example2 = () => { From 782759d01b25b8cbe9493c6b4afe690f2ed43e18 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 16:44:39 +0100 Subject: [PATCH 16/73] Added extra detail to the comment --- .../70-as-prop-with-custom-components.problem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 197b258..3850d0b 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 @@ -8,7 +8,14 @@ import { Equal, Expect } from "../helpers/type-utils"; * * 1. Figure out the correct typings for the `Wrapper` component. * - * The solution uses generics, and the ComponentType helper. + * The solution uses: + * + * - Generics + * - ElementType + * - One of the following: + * - ComponentPropsWithoutRef + * - ComponentPropsWithRef + * - ComponentProps */ export const Wrapper = (props: { as: unknown }) => { const Comp = props.as; From 9ac77a12b5e698bdece3f7cdc1ba21c37ea6a4e1 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 7 Aug 2023 16:45:14 +0100 Subject: [PATCH 17/73] Changed the way that 70 starts out --- .../70-as-prop-with-custom-components.problem.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 3850d0b..fbd721f 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 @@ -17,9 +17,14 @@ import { Equal, Expect } from "../helpers/type-utils"; * - ComponentPropsWithRef * - ComponentProps */ -export const Wrapper = (props: { as: unknown }) => { - const Comp = props.as; - return ; +export const Wrapper = ( + props: { + as: TAs; + } & React.ComponentProps, +) => { + const Comp = props.as as string; + + return ; }; /** From 35b5af3abfdf8caf4dda11bb79c5abbbce9281c3 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Tue, 8 Aug 2023 09:02:34 +0100 Subject: [PATCH 18/73] Changed 70 solution --- .../70-as-prop-with-custom-components.solution.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx b/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx index 95dc6ec..e4238f4 100644 --- a/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx +++ b/src/08-advanced-patterns/70-as-prop-with-custom-components.solution.tsx @@ -1,12 +1,13 @@ -import React, { ComponentPropsWithoutRef, ElementType } from "react"; +import React, { ElementType } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -export const Wrapper = ( +export const Wrapper = ( props: { - as: T; - } & ComponentPropsWithoutRef, + as: TAs; + } & React.ComponentPropsWithoutRef, ) => { const Comp = props.as; + return ; }; From 2ca5d6b11536ee6803125b54871a87122261ad5e Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Tue, 8 Aug 2023 09:33:26 +0100 Subject: [PATCH 19/73] Changed the start of 71 --- .../71-as-prop-with-default.problem.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 9be70e8..28b2e79 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,13 +1,13 @@ -import { ComponentPropsWithoutRef, ElementType } from "react"; +import { ElementType } from "react"; import { Equal, Expect } from "../helpers/type-utils"; -export const Link = ( +export const Link = ( props: { - as: T; - } & ComponentPropsWithoutRef, + as: TAs; + } & React.ComponentPropsWithoutRef, ) => { - const Comp = props.as; - return ; + const { as: Comp = "a", ...rest } = props; + return ; }; /** From 27a1313689c4137b3770d081893f80bc923d12c9 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Tue, 8 Aug 2023 09:48:31 +0100 Subject: [PATCH 20/73] Improved 72 --- .../72-as-prop-with-forward-ref.problem.tsx | 10 ++++++---- .../72-as-prop-with-forward-ref.solution.tsx | 11 +++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx b/src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx index bf28cd4..f05136d 100644 --- a/src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx +++ b/src/08-advanced-patterns/72-as-prop-with-forward-ref.problem.tsx @@ -1,6 +1,7 @@ import { ComponentPropsWithoutRef, ElementType, + ForwardedRef, forwardRef, useRef, } from "react"; @@ -13,13 +14,14 @@ import { Equal, Expect } from "../helpers/type-utils"; * So, don't feel bad if you don't find it at all. */ -export const UnwrappedLink = ( +export const UnwrappedLink = ( props: { - as?: T; - } & ComponentPropsWithoutRef, + as?: TAs; + } & ComponentPropsWithoutRef, + ref: ForwardedRef, ) => { const { as: Comp = "a", ...rest } = props; - return ; + return ; }; const Link = forwardRef(UnwrappedLink); diff --git a/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx b/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx index 9b5d9f2..4a6f479 100644 --- a/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx +++ b/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx @@ -1,4 +1,10 @@ -import { ComponentProps, ElementType, forwardRef, useRef } from "react"; +import { + ComponentProps, + ElementType, + ForwardedRef, + forwardRef, + useRef, +} from "react"; import { Equal, Expect } from "../helpers/type-utils"; // Added fixedForwardRef from a previous exercise @@ -19,9 +25,10 @@ function UnwrappedLink( props: { as?: T; } & DistributiveOmit, "as">, + ref: ForwardedRef, ) { const { as: Comp = "a", ...rest } = props; - return ; + return ; } const Link = fixedForwardRef(UnwrappedLink); From aa01465315300f8504f8de6e7ee9764c54d8db4b Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Tue, 8 Aug 2023 10:09:59 +0100 Subject: [PATCH 21/73] Fixed 72 --- .../72-as-prop-with-forward-ref.solution.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx b/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx index 4a6f479..5b915de 100644 --- a/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx +++ b/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx @@ -1,5 +1,5 @@ import { - ComponentProps, + ComponentPropsWithRef, ElementType, ForwardedRef, forwardRef, @@ -21,15 +21,18 @@ type DistributiveOmit = T extends any ? Omit : never; -function UnwrappedLink( +export const UnwrappedLink = ( props: { - as?: T; - } & DistributiveOmit, "as">, + as?: TAs; + } & DistributiveOmit< + ComponentPropsWithRef, + "as" + >, ref: ForwardedRef, -) { +) => { const { as: Comp = "a", ...rest } = props; return ; -} +}; const Link = fixedForwardRef(UnwrappedLink); From 616b92de88d47d0a40a81c3f0c12db606fce592f Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 14 Aug 2023 14:08:58 +0100 Subject: [PATCH 22/73] Removed dead file --- ...42-passing-types-to-components.problem.tsx | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 src/05-generics/42-passing-types-to-components.problem.tsx diff --git a/src/05-generics/42-passing-types-to-components.problem.tsx b/src/05-generics/42-passing-types-to-components.problem.tsx deleted file mode 100644 index 5bba74e..0000000 --- a/src/05-generics/42-passing-types-to-components.problem.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ReactNode } from "react"; -import { Equal, Expect } from "../helpers/type-utils"; - -interface TableProps { - rows: T[]; - renderRow: (row: T) => ReactNode; -} - -export const Table = (props: TableProps) => { - return ( -
- - {props.rows.map((row) => ( - {props.renderRow(row)} - ))} - -
- ); -}; - -interface User { - id: number; - name: string; - age: number; -} - -/** - * Below, we'd love to ensure that the type of `row` is `User`. How - * do we do this? - */ -<> - { - type test = Expect>; - return ; - }} - /> -
{row.name}
{ - type test = Expect>; - return ; - }} - >
{row.name}
-; From 716502ded0b4a17ca09d5f5618a13d815ddd17c0 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 12:47:13 +0100 Subject: [PATCH 23/73] Improved explainer --- .../73-react-hook-form.explainer.tsx | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/09-external-libraries/73-react-hook-form.explainer.tsx b/src/09-external-libraries/73-react-hook-form.explainer.tsx index 60d4f29..1c6e0e9 100644 --- a/src/09-external-libraries/73-react-hook-form.explainer.tsx +++ b/src/09-external-libraries/73-react-hook-form.explainer.tsx @@ -7,18 +7,27 @@ import { Equal, Expect } from "../helpers/type-utils"; * * Investigate why this is, and what TFieldValues is being used for. */ -const formWithValues = useForm({ - defaultValues: { - firstName: "", - lastName: "", - }, -}); +const Example1 = () => { + const form = useForm({ + defaultValues: { + firstName: "", + lastName: "", + }, + }); -const values = formWithValues.getValues(); - -type test = Expect< - Equal ->; + return ( +
{ + type test = Expect< + Equal + >; + })} + > + + + + ); +}; /** * 2. When you don't pass a default value, the return type of getValues is @@ -27,8 +36,17 @@ type test = Expect< * Investigate why this is, and what type FieldValues is. */ -const formWithoutValues = useForm(); - -const values2 = formWithoutValues.getValues(); +const Example2 = () => { + const form = useForm({}); -type test2 = Expect>; + return ( +
{ + type test = Expect>; + })} + > + + + + ); +}; From 21047abaf0350f00e673e2d361f6624e3f030e25 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 12:56:04 +0100 Subject: [PATCH 24/73] Changed to P/S --- ...ner.tsx => 73-react-hook-form.problem.tsx} | 2 +- .../73-react-hook-form.solution.tsx | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) rename src/09-external-libraries/{73-react-hook-form.explainer.tsx => 73-react-hook-form.problem.tsx} (97%) create mode 100644 src/09-external-libraries/73-react-hook-form.solution.tsx diff --git a/src/09-external-libraries/73-react-hook-form.explainer.tsx b/src/09-external-libraries/73-react-hook-form.problem.tsx similarity index 97% rename from src/09-external-libraries/73-react-hook-form.explainer.tsx rename to src/09-external-libraries/73-react-hook-form.problem.tsx index 1c6e0e9..f19e579 100644 --- a/src/09-external-libraries/73-react-hook-form.explainer.tsx +++ b/src/09-external-libraries/73-react-hook-form.problem.tsx @@ -37,7 +37,7 @@ const Example1 = () => { */ const Example2 = () => { - const form = useForm({}); + const form = useForm(); return (
{ + const form = useForm({ + values: { + firstName: "", + lastName: "", + }, + }); + + return ( + { + type test = Expect< + Equal + >; + })} + > + + + + ); +}; + +const Example2 = () => { + const form = useForm(); + + return ( +
{ + type test = Expect>; + })} + > + + + + ); +}; From 676b6303dbdfb9d5b3641df1324bb5d1cfccbed2 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 13:11:19 +0100 Subject: [PATCH 25/73] WIP --- .../73-react-hook-form.problem.tsx | 27 ++++++++++++ .../73-react-hook-form.solution.tsx | 42 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/09-external-libraries/73-react-hook-form.problem.tsx b/src/09-external-libraries/73-react-hook-form.problem.tsx index f19e579..94153a7 100644 --- a/src/09-external-libraries/73-react-hook-form.problem.tsx +++ b/src/09-external-libraries/73-react-hook-form.problem.tsx @@ -50,3 +50,30 @@ const Example2 = () => { ); }; + +/** + * 3. If we don't pass default values, how do we get + * react-hook-form to understand what type our fields are? + */ + +type FormValues = { + firstName: string; + lastName: string; +}; + +const Example3 = () => { + const form = useForm(); + + return ( +
{ + type test = Expect>; + })} + > + + + {/* @ts-expect-error */} + + + ); +}; diff --git a/src/09-external-libraries/73-react-hook-form.solution.tsx b/src/09-external-libraries/73-react-hook-form.solution.tsx index 7e84656..db01003 100644 --- a/src/09-external-libraries/73-react-hook-form.solution.tsx +++ b/src/09-external-libraries/73-react-hook-form.solution.tsx @@ -1,9 +1,15 @@ import { FieldValues, useForm } from "react-hook-form"; import { Equal, Expect } from "../helpers/type-utils"; +/** + * 1. When you provide default values to useForm, the return type of getValues + * gets inferred as the shape of those values. + * + * Investigate why this is, and what TFieldValues is being used for. + */ const Example1 = () => { const form = useForm({ - values: { + defaultValues: { firstName: "", lastName: "", }, @@ -23,6 +29,13 @@ const Example1 = () => { ); }; +/** + * 2. When you don't pass a default value, the return type of getValues is + * inferred as FieldValues. + * + * Investigate why this is, and what type FieldValues is. + */ + const Example2 = () => { const form = useForm(); @@ -37,3 +50,30 @@ const Example2 = () => { ); }; + +/** + * 3. If we don't pass default values, how do we get + * react-hook-form to understand what type our fields are? + */ + +type FormValues = { + firstName: string; + lastName: string; +}; + +const Example3 = () => { + const form = useForm(); + + return ( +
{ + type test = Expect>; + })} + > + + + {/* @ts-expect-error */} + + + ); +}; From d9ee27ef4c968d52c69198b218952a63ed5f92ac Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 13:22:04 +0100 Subject: [PATCH 26/73] Added another hint to 74 --- .../74-react-hook-form-wrapper.problem.tsx | 4 ++++ 1 file changed, 4 insertions(+) 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 f24d09a..cc6591b 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 @@ -7,6 +7,10 @@ import { Equal, Expect, Extends } from "../helpers/type-utils"; * We want to change the API slightly so that only certain methods are * exposed. We also want to make sure that defaultValues is ALWAYS * required. + * + * A clue: you'll need this line of code: + * + * defaultValues as DefaultValues */ const useCustomForm = (defaultValues: any) => { const form = useForm({ From a61f352a02860680e42dd01ebd3b48fc13891b15 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 13:29:58 +0100 Subject: [PATCH 27/73] Changed 74 solution --- ...74-react-hook-form-wrapper.solution.1.tsx} | 9 --- .../74-react-hook-form-wrapper.solution.2.tsx | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 9 deletions(-) rename src/09-external-libraries/{74-react-hook-form-wrapper.solution.tsx => 74-react-hook-form-wrapper.solution.1.tsx} (77%) create mode 100644 src/09-external-libraries/74-react-hook-form-wrapper.solution.2.tsx diff --git a/src/09-external-libraries/74-react-hook-form-wrapper.solution.tsx b/src/09-external-libraries/74-react-hook-form-wrapper.solution.1.tsx similarity index 77% rename from src/09-external-libraries/74-react-hook-form-wrapper.solution.tsx rename to src/09-external-libraries/74-react-hook-form-wrapper.solution.1.tsx index 0053076..6857b57 100644 --- a/src/09-external-libraries/74-react-hook-form-wrapper.solution.tsx +++ b/src/09-external-libraries/74-react-hook-form-wrapper.solution.1.tsx @@ -1,17 +1,8 @@ import { DefaultValues, FieldValues, useForm } from "react-hook-form"; import { Equal, Expect, Extends } from "../helpers/type-utils"; -/** - * We add TValues as a generic to our hook, and use it to type the defaultValues. - * - * We constrain it to FieldValues so that it's assignable to useForm's defaultValues. - */ const useCustomForm = (defaultValues: TValues) => { const form = useForm({ - /** - * There's a strange papercut here where we have to cast defaultValues as - * DefaultValues to get it to work. - */ defaultValues: defaultValues as DefaultValues, }); diff --git a/src/09-external-libraries/74-react-hook-form-wrapper.solution.2.tsx b/src/09-external-libraries/74-react-hook-form-wrapper.solution.2.tsx new file mode 100644 index 0000000..663ed01 --- /dev/null +++ b/src/09-external-libraries/74-react-hook-form-wrapper.solution.2.tsx @@ -0,0 +1,61 @@ +import { + DefaultValues, + FieldValues, + UseFormGetValues, + UseFormHandleSubmit, + UseFormRegister, + useForm, +} from "react-hook-form"; +import { Equal, Expect, Extends } from "../helpers/type-utils"; + +const useCustomForm = ( + defaultValues: TValues, +): { + register: UseFormRegister; + handleSubmit: UseFormHandleSubmit; + getValues: UseFormGetValues; +} => { + const form = useForm({ + defaultValues: defaultValues as DefaultValues, + }); + + return { + register: form.register, + handleSubmit: form.handleSubmit, + getValues: form.getValues, + }; +}; + +// ---- TESTS ---- + +// @ts-expect-error defaultValues is required +useCustomForm(); + +useCustomForm( + // @ts-expect-error defaultValues must be an object + 2, +); + +const customForm = useCustomForm({ + firstName: "", + lastName: "", +}); + +customForm.handleSubmit((values) => { + type test = Expect< + // Expect that inside handleSubmit, it's inferred as + // { firstName: string; lastName: string } + Extends< + { + firstName: string; + lastName: string; + }, + typeof values + > + >; +}); + +// Expect that only the methods we want are exposed +type test = Expect< + Equal +>; From 46043fb3190b071ac7fc66387dbcb467a6f52a7b Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 21 Aug 2023 13:49:34 +0100 Subject: [PATCH 28/73] Fixed 75 solution --- src/09-external-libraries/75-react-select.solution.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/09-external-libraries/75-react-select.solution.tsx b/src/09-external-libraries/75-react-select.solution.tsx index 42cfd96..1af56c8 100644 --- a/src/09-external-libraries/75-react-select.solution.tsx +++ b/src/09-external-libraries/75-react-select.solution.tsx @@ -1,11 +1,8 @@ import ReactSelect, { GroupBase, Props } from "react-select"; import { Equal, Expect } from "../helpers/type-utils"; -/** - * You need to mimic the exact type of the Props type exported by react-select. - */ export const Select = < - Option, + Option = unknown, IsMulti extends boolean = false, Group extends GroupBase