Skip to content

Commit 7902bf0

Browse files
arvindcheenuswyx
and
swyx
authored
Update README.md with Troubleshooting Changes (#260)
* Update README.md with Types Added changes to Troubleshooting types from #254 to README.md * Update README.md * fix prettier Co-authored-by: swyx <[email protected]>
1 parent da85af2 commit 7902bf0

File tree

1 file changed

+137
-10
lines changed

1 file changed

+137
-10
lines changed

Diff for: README.md

+137-10
Original file line numberDiff line numberDiff line change
@@ -1544,31 +1544,158 @@ let baz2: SubIsntType2 = {
15441544
15451545
What's more annoying than modules with unexported types? Modules that are **untyped**!
15461546
1547-
Fret not! It's just a simple two step process.
1547+
Fret not! There are more than a couple of ways in which you can solve this problem.
15481548
1549-
- Create a new type declaration file, say `typedec.d.ts`– if you don't already have one. Ensure that the path to file is resolvable by Typescript. To do so, check the `include` array in the `tsconfig.json` file at the root of your directory.
1549+
A **lazier** way would be to create a new type declaration file, say `typedec.d.ts`– if you don't already have one. Ensure that the path to file is resolvable by TypeScript by checking the `include` array in the `tsconfig.json` file at the root of your directory.
15501550
15511551
```json
15521552
// inside tsconfig.json
15531553
{
1554-
...
1555-
"include": [
1556-
"src" // automatically resolves if path to declaration is src/typedec.d.ts
1557-
],
1558-
...
1554+
// ...
1555+
"include": [
1556+
"src" // automatically resolves if the path to declaration is src/typedec.d.ts
1557+
]
1558+
// ...
15591559
}
15601560
```
15611561
1562-
- Add the `declare` syntax for your desired module, say `my-untyped-module`– to the declaration file.
1562+
Within this file, add the `declare` syntax for your desired module, say `my-untyped-module`– to the declaration file:
15631563
15641564
```ts
15651565
// inside typedec.d.ts
15661566
declare module "my-untyped-module";
15671567
```
15681568
1569-
This one-line alone is enough if you just need it to work without errors. If you need to, you can include your type definitions inferred from the untyped module's source or docs within curly braces following this declaration.
1569+
This one-liner alone is enough if you just need it to work without errors. A even hackier, write-once-and-forget way would be to use `"*"` instead which would then apply the `Any` type for all existing and future untyped modules.
1570+
1571+
This solution works well as a workaround if you have less than a couple untyped modules. Anything more, you now have a ticking type-bomb in your hands. The only way of circumventing this problem would be to define the missing types for those untyped modules as explained in the following sections.
1572+
1573+
### Typing Exported Hooks
1574+
1575+
Typing Hooks is just like typing pure functions.
1576+
1577+
The following steps work under two assumptions:
1578+
1579+
- You have already created a type declaration file as stated earlier in the section.
1580+
- You have access to the source code - specifically the code that directly exports the functions you will be using. In most cases, it would be housed in an `index.js` file.
1581+
Typically you need a minimum of **two** type declarations (one for **Input Prop** and the other for **Return Prop**) to define a hook completely. Suppose the hook you wish to type follows the following structure,
1582+
1583+
```js
1584+
// ...
1585+
const useUntypedHook = (prop) => {
1586+
// some processing happens here
1587+
return {
1588+
/* ReturnProps */
1589+
};
1590+
};
1591+
export default useUntypedHook;
1592+
```
1593+
1594+
then, your type declaration should most likely follow the following syntax.
1595+
1596+
```ts
1597+
declare module 'use-untyped-hook' {
1598+
export interface InputProps { ... } // type declaration for prop
1599+
export interface ReturnProps { ... } // type declaration for return props
1600+
export default function useUntypedHook(
1601+
prop: InputProps
1602+
// ...
1603+
): ReturnProps;
1604+
}
1605+
```
1606+
1607+
<details>
1608+
<summary>
1609+
1610+
For instance, the [useDarkMode hook](https://github.com/donavon/use-dark-mode) exports the functions that follows a similar structure.
1611+
1612+
</summary>
1613+
1614+
```js
1615+
// inside src/index.js
1616+
const useDarkMode = (
1617+
initialValue = false, // -> input props / config props to be exported
1618+
{
1619+
// -> input props / config props to be exported
1620+
element,
1621+
classNameDark,
1622+
classNameLight,
1623+
onChange,
1624+
storageKey = "darkMode",
1625+
storageProvider,
1626+
global,
1627+
} = {}
1628+
) => {
1629+
// ...
1630+
return {
1631+
// -> return props to be exported
1632+
value: state,
1633+
enable: useCallback(() => setState(true), [setState]),
1634+
disable: useCallback(() => setState(false), [setState]),
1635+
toggle: useCallback(() => setState((current) => !current), [setState]),
1636+
};
1637+
};
1638+
export default useDarkMode;
1639+
```
1640+
1641+
As the comments suggest, exporting these config props and return props following the aforementioned structure will result in the following type export.
1642+
1643+
```ts
1644+
declare module "use-dark-mode" {
1645+
/**
1646+
* A config object allowing you to specify certain aspects of `useDarkMode`
1647+
*/
1648+
export interface DarkModeConfig {
1649+
classNameDark?: string; // A className to set "dark mode". Default = "dark-mode".
1650+
classNameLight?: string; // A className to set "light mode". Default = "light-mode".
1651+
element?: HTMLElement; // The element to apply the className. Default = `document.body`
1652+
onChange?: (val?: boolean) => void; // Overide the default className handler with a custom callback.
1653+
storageKey?: string; // Specify the `localStorage` key. Default = "darkMode". Set to `null` to disable persistent storage.
1654+
storageProvider?: WindowLocalStorage; // A storage provider. Default = `localStorage`.
1655+
global?: Window; // The global object. Default = `window`.
1656+
}
1657+
/**
1658+
* An object returned from a call to `useDarkMode`.
1659+
*/
1660+
export interface DarkMode {
1661+
readonly value: boolean;
1662+
enable: () => void;
1663+
disable: () => void;
1664+
toggle: () => void;
1665+
}
1666+
/**
1667+
* A custom React Hook to help you implement a "dark mode" component for your application.
1668+
*/
1669+
export default function useDarkMode(
1670+
initialState?: boolean,
1671+
config?: DarkModeConfig
1672+
): DarkMode;
1673+
}
1674+
```
1675+
1676+
</details>
1677+
1678+
### Typing Exported Components
1679+
1680+
In case of typing untyped class components, there's almost no difference in approach except for the fact that after declaring the types, you export the extend the type using `class UntypedClassComponent extends React.Component<UntypedClassComponentProps, any> {}` where `UntypedClassComponentProps` holds the type declaration.
1681+
1682+
For instance, [sw-yx's Gist on React Router 6 types](https://gist.github.com/sw-yx/37a6a3d248c2d4031801f0d568904df8) implemented a similar method for typing the then untyped RR6.
1683+
1684+
```ts
1685+
declare module "react-router-dom" {
1686+
import * as React from 'react';
1687+
// ...
1688+
type NavigateProps<T> = {
1689+
to: string | number,
1690+
replace?: boolean,
1691+
state?: T
1692+
}
1693+
//...
1694+
export class Navigate<T = any> extends React.Component<NavigateProps<T>>{}
1695+
// ...
1696+
```
15701697
1571-
If you're working with untyped class components in React, you can also checkout this [short post](https://templecoding.com/blog/2016/03/31/creating-typescript-typings-for-existing-react-components) as a reference.
1698+
For more information on creating type definitions for class components, you can refer to this [post](https://templecoding.com/blog/2016/03/31/creating-typescript-typings-for-existing-react-components) for reference.
15721699
15731700
# Troubleshooting Handbook: Images and other non-TS/TSX files
15741701

0 commit comments

Comments
 (0)