-
Notifications
You must be signed in to change notification settings - Fork 0
/
type_guard.ts
143 lines (104 loc) · 3.95 KB
/
type_guard.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
type Base = { id: string } | string;
const getStringFromValue = <TValue extends Base>(value: TValue) => {
if (typeof value === 'string') {
// here "value" will be the type of "string"
return value;
}
// here "value" will be the type of "NOT string", in our case { id: string }
return value.id;
};
// ==========================================================================================
type Base = { id: string } | string;
type GenericSelectProps<TValue> = {
formatLabel: (value: TValue) => string;
onChange: (value: TValue) => void;
values: TValue[];
};
const getStringFromValue = <TValue extends Base>(value: TValue) => {
if (typeof value === 'string') return value;
return value.id;
};
export const GenericSelect = <TValue extends Base>(props: GenericSelectProps<TValue>) => {
const { values, onChange, formatLabel } = props;
const onSelectChange = (e) => {
const val = values.find((value) => getStringFromValue(value) === e.target.value);
if (val) onChange(val);
};
return (
<select onChange= { onSelectChange } >
{
values.map((value) => (
<option key= { getStringFromValue(value) } value = { getStringFromValue(value) } >
{ formatLabel(value) }
< /option>
))
}
< /select>
);
};
// usage
const tabs = ['Books', 'Movies', 'Laptops'];
const getSelect = (tab: string) => {
switch (tab) {
case 'Books':
return <GenericSelect<Book> onChange={ (value) => console.info(value) } values = { books } />;
case 'Movies':
return <GenericSelect<Movie> onChange={ (value) => console.info(value) } values = { movies } />;
case 'Laptops':
return <GenericSelect<Laptop> onChange={ (value) => console.info(value) } values = { laptops } />;
}
}
const Tabs = () => {
const [tab, setTab] = useState<string>(tabs[0]);
const select = getSelect(tab);
return (
<>
<GenericSelect<string> onChange= {(value) => setTab(value)} values = { tabs } />
{ select }
< />
);
};
// =============================================================================================
const allTabs = ['Books', 'Movies', 'Laptops'];
type Tabs = typeof allTabs;
// Tabs will be string[];
const allTabs2 = ['Books', 'Movies', 'Laptops'] as const;
// readonly ["Books", "Movies", "Laptops"]
const allTabs3 = ['Books', 'Movies', 'Laptops'] as const;
type TabsV = typeof allTabs3[number];
// "Books" | "Movies" | "Laptops"
// =============================================================================================
type T = { id: string };
type Base = T | string;
export const isStringValue = <TValue extends T>(value: TValue | string): value is string => {
return typeof value === 'string';
};
const getStringFromValue = <TValue extends Base>(value: TValue) => {
if (isStringValue(value)) {
// do something with the string
}
// do something with the object
};
export type DataTypes = Book | Movie | Laptop | string;
export const isBook = (value: DataTypes): value is Book => {
return typeof value !== 'string' && 'id' in value && 'author' in value;
};
export const isMovie = (value: DataTypes): value is Movie => {
return typeof value !== 'string' && 'id' in value && 'releaseDate' in value && 'title' in value;
};
export const isLaptop = (value: DataTypes): value is Laptop => {
return typeof value !== 'string' && 'id' in value && 'model' in value;
};
const formatLabel = (value: DataTypes) => {
// value will be always Book here since isBook has predicate attached
if (isBook(value)) return value.author;
// value will be always Movie here since isMovie has predicate attached
if (isMovie(value)) return value.releaseDate;
// value will be always Laptop here since isLaptop has predicate attached
if (isLaptop(value)) return value.model;
return value;
};
// somewhere in the render
<GenericSelect<Book> ... formatLabel={formatLabel} />
<GenericSelect<Movie> ... formatLabel={formatLabel} />
<GenericSelect<Laptop> ... formatLabel={formatLabel} />