Skip to content

Commit 63603dc

Browse files
committed
Added more explanation to forwardRef
1 parent da56f7d commit 63603dc

File tree

2 files changed

+84
-2
lines changed

2 files changed

+84
-2
lines changed

src/08-advanced-patterns/55-forward-ref-with-generics.explainer.1.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,31 @@ type Props<T> = {
66
renderRow: (item: T) => React.ReactNode;
77
};
88

9+
/**
10+
* Try uncommenting the following code. You'll see that the type of the `row`
11+
* prop is inferred to be `string`.
12+
*
13+
* This suggestion is from Stefan Baumgartner:
14+
*
15+
* https://fettblog.eu/typescript-react-generic-forward-refs/#option-3%3A-augment-forwardref
16+
*/
17+
918
// declare module "react" {
1019
// function forwardRef<T, P = {}>(
11-
// render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
12-
// ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
20+
// render: (props: P, ref: React.Ref<T>) => React.ReactNode | null,
21+
// ): (props: P & React.RefAttributes<T>) => React.ReactNode | null;
1322
// }
1423

24+
/**
25+
* By ditching defaultProps and propTypes on the type passed ro render,
26+
* we can make use of something called 'higher order function type
27+
* inference':
28+
*
29+
* https://github.com/microsoft/TypeScript/pull/30215
30+
*
31+
* By doing it this way, we preserve the generic context of the function
32+
* being passed in.
33+
*/
1534
export const Table = <T,>(props: Props<T>, ref: ForwardedRef<any>) => {
1635
return null;
1736
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Equal, Expect } from "../helpers/type-utils";
2+
3+
/**
4+
* Here's a detailed breakdown on why forwardRef doesn't work.
5+
*/
6+
7+
/**
8+
* 1. We create a type that represents a function, but with
9+
* some other attributes.
10+
*/
11+
type FuncExpected<Argument> = {
12+
(arg: Argument): Argument;
13+
someOtherThing?: string;
14+
};
15+
16+
/**
17+
* 2. We create a function that takes a function as an argument,
18+
* and infers the position of Argument.
19+
*
20+
* This function doesn't do anything at runtime - it just returns
21+
* the function that was passed in. But it behaves similarly to
22+
* forwardRef.
23+
*/
24+
const forwardRefShim = <Argument>(func: FuncExpected<Argument>) => {
25+
return (arg: Argument) => func(arg);
26+
};
27+
28+
/**
29+
* 3. We create an identity function, that just takes in an argument
30+
* and returns it.
31+
*/
32+
const identityFunc = <Argument>(arg: Argument) => {
33+
return arg;
34+
};
35+
36+
/**
37+
* 4. As you can see, when it's not wrapped, identityFunc returns
38+
* the type that we pass in, 123.
39+
*/
40+
const result1 = identityFunc(123);
41+
42+
type test1 = Expect<Equal<typeof result1, 123>>;
43+
44+
/**
45+
* 5. But when we wrap it in forwardRefShim, it loses its powers
46+
* of inference! Just like forwardRef.
47+
*/
48+
const wrappedIdentityFunc = forwardRefShim(identityFunc);
49+
50+
const result2 = wrappedIdentityFunc(123);
51+
52+
type test2 = Expect<Equal<typeof result2, 123>>;
53+
54+
/**
55+
* 6. Here's the really crazy part. Go back up to FuncExpected.
56+
* Comment out the someOtherThing property.
57+
*
58+
* It now works! This is because when a function is _just_ a function,
59+
* TypeScript uses its higher-order function powers on it. But when
60+
* it has other properties, it doesn't.
61+
*
62+
* Bizarre!
63+
*/

0 commit comments

Comments
 (0)