How to add custom types to a javascript library
Tags: javascript, typescript, teaful, react
Published: 2021-12-06 16:12:52

Few weeks ago, I started contributing to an open source library called [Teaful](https://github.com/teafuljs/teaful), a Tiny, EAsy, and powerFUL for React state management, with ambitious roadmap. Now `Teaful` reached more than **500 GitHub ⭐️ Stars**, the library and his community are growing fast.

That means issues and pull requests are growing as well, and soon we realized that we need to improve dev-experience and provide [tools](https://dev.to/aralroca/teaful-devtools-released-37lp) for that reason.

Bear this in mind, implement custom types to allow all the benefits from `TypeScript` at `Teaful` is a big step on that way.

(Yes, I know, migrate a library to pure ts probably is a better solution, and it's on our [roadmap](https://github.com/teafuljs/teaful#roadmap-) before 1.0.0)


![Autocomplete code gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxj91tgnh9as4jn3ualx.gif)

## Index
1. [Avoiding auto generated types](#avoiding-auto-generated-types)
2. [Where to place custom types](#where-to-place-custom-types)
3. [Create custom types](#create-custom-types)
4. [Conclusions](#conclusions)

## Avoiding auto generated types
In our case, an auto-generated custom type full of `any` was useless. So, we started implementing custom types.

We're using `microbundle`, they provide a flag to avoid auto-generate types, `--no-generateTypes`. Microbundle, according to docs, generally respect your TypeScript config at `tsconfig.json` (you can read more about [here](https://github.com/developit/microbundle#using-with-typescript)), but at this moment we don't need a specific configuration for `TypeScript`

Then we can inform on `package.json` where are our custom types with `"types": "folder/index.d.ts"`.

## Where to place custom types
Create a file with extension `.d.ts` , generally you'll put this file on `dist` folder. Now here you can add your custom types.

## Create custom types
Here  I'm going to explain how we created custom types specifics for `Teaful` and why some decisions were taken, if you're reading this to know how to add custom types to your js library and already know about `TypeScript`, feel free to skip this section.

### InitialStore
The `store` is where `Teaful` saves data, is a key-value object (you can have more than one store). Easy to type:

```javascript
type initialStoreType = Record<string, any>;
```

So far so good, nothing strange here. We want to store anything, and all keys will be string.

Then things become more complicated. In this article only things about creating types will be explained, so if you want to know more about how to implement `Teaful` I strongly recommend visit the [README at github](https://github.com/teafuljs/teaful#teaful). 

### Hook Return
To create a new value on store is pretty similar to `useState` from `React`. Let's see an example:

```javascript
const [username, setUsername] = useStore.username();
```

Easy right? Ok, so what have we here? `useStore` returns an array of two elements (Yes! Like useState!), the element in the store and the function to update it. 

The type we need:
```javascript
type HookReturn<T> = [T, (value: T | ((value: T) => T | undefined | null) ) => void];
```

If you're not familiar with TS, this could look a little cryptic. We're creating a new type called `HookReturn` which gets a generic type we called '`T`' (from Type, but you can use any name). 

This type is a tuple(a data structure that is an ordered list of elements with a fixed length, because we aren't going to add more elements for the return of our `useStore`), where first element is `T`, because we want to return a value with specific type that we don't know at the moment of creating the type, but we want to ensure, for example, that the setter function (the second element on this tuple) will get the same type we are using for the first element as param.

Then, let's pay attention on the second element of our tuple.
```javascript
(value: T | ((value: T) => T | undefined | null) ) => void
``` 
Here, our type is a function that returns nothing ( `() => void`), but accepts one param (`value: T | ((value: T) => T | undefined | null) `), and this param could be a value of type `T`, or a function that get a value of type `T` and returns `null`, `undefined` or a value of type `T` (`(value: T) => T | undefined | null`). 

What this means? what are we allowing here with this type? Ok, let's imagine a counter:

```javascript
const [counter, setCounter] = useStore.counter();

//allowed by T
setCounter(counter+1);
//allowed by  ((value: T) => T | undefined | null)
setCounter((counter) => counter*2))
setCounter((counter) => undefined)
setCounter((counter) => null)
```
Yes, Teaful accepts a function as param on the setter function.

### Hook type
When you create/call a new property with [useStore](https://github.com/teafuljs/teaful#usestore-hook), you call `useStore.[newProperty]()`. This accepts two optional params, first for `initialValue`, and the second one is for `updateValue` (a function to update the store property indicated with the `proxy`). The hook looks easy to create here:

```javascript
type Hook<S> = (
    initial?: S,
    onAfterUpdate?: afterCallbackType<S>
) => HookReturn<S>;
```
Both optional, but the second one is a specific function. Type `onAfterUpdate`, is a function with two params: `store` before and after the changes, both will be same type, extending our `initialStore` type.

```javascript
type afterCallbackType<S extends initialStoreType> = (
    param: { store: S; prevStore: S; }
) => void
```

Finally, our type `Hook` will return a tuple ` [property,setter]`, so indeed, we're going to return our custom type `HookReturn` with our generic type. If we create a number, have sense to take care about number type in all places, for the initial value, the returned tuple... etc.

### Hoc type
Teaful allows to use it as Hoc (as connect on Redux, code explain it by itself):
```javascript
const { withStore } = createStore({ count: 0 });
class Counter extends Component {
  render() {
  const [store, setStore] = this.props.store;
    return (
      // [...]
    );
  }
}

// Similar to useStore()
const CounterWithStore = withStore(Counter);
```

The HOC [`withStore`](https://github.com/teafuljs/teaful#withstore-hoc) wraps a `Component` and returns the component with a prop called store. A second parameter for initial value is allowed, and a third one for `onAfterUpdate` callback.
```javascript
type HocFunc<S, R extends React.ComponentClass = React.ComponentClass> = (
       component: R,
       initial?: S,
       onAfterUpdate?: afterCallbackType<S>
) => R;
```
We need two generic types, one for initial value and `onAfterUpdate` (both will use same generic, but `onAfterUpdate` will have a specific type, explained later) and the other one for `React` component to wrap that would be the same for the return, because we want the same component but with a new prop called store.

Look at the `R` type, is extending `React.ComponentClass` (type provided by `React`). This means that we are taking profit from that type and including it in our generic type called `R`. 

Why extending component class only and not functional component? 

Well, we didn't found a single situation when we wanted to wrap any component that doesn't extend Class with a HOC to get the store.

Ok, third type: `onAfterUpdate`.  Here we need a function with two params store before and after the changes, both will be same type, extending our `initialStore` type. Same as first hook, we reuse same type for all callbacks params

Now we only have to export the a type to use
```javascript
  export type Hoc<S> = { store: HookReturn<S> };
```

### HookDry type
`Teaful` provides a helper called [`getStore`](https://github.com/teafuljs/teaful#getstore-helper), like useStore but:

- It does not make a subscription. So it is no longer a hook and you can use it as a helper wherever you want.
- It's not possible to register events that are executed after a change.

This means we don't want same as `useStore `type, we return the same but we want to ensure we don't accept a second param as callback. Let's create another one:

```javascript
  type HookDry<S> = (initial?: S) => HookReturn<S>;
```

The return is clear, same as Hook.

### Let's type useStore, getStore and withStore
Ok, now we have almost all the work done. A custom type is needed for each tool, `useStore`, `getStore `and `withStore`:

```javascript
  type getStoreType<S extends initialStoreType> = {
    [key in keyof S]: S[key] extends initialStoreType
      ? useStoreType<S[key]> & HookDry<S[key]> : HookDry<S[key]>;
  };

  type useStoreType<S extends initialStoreType> = {
    [key in keyof S]: S[key] extends initialStoreType
      ? useStoreType<S[key]> & Hook<S[key]> : Hook<S[key]>;
  };

  type withStoreType<S extends initialStoreType> = {
    [key in keyof S]: S[key] extends initialStoreType
      ? withStoreType<S[key]> & HocFunc<S>
      : HocFunc<S>;
  };
```
The [`keyOf`](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html) type operator ensures that our `property` will exist on `store`.

The ternary here looks weird if you're not familiar with `Typescript`, is used for [conditional-types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html). The logic shared in three types is, get a generic type (`S`, that extends our `initialStoreType`), then get a `key `that must be on `S` (the property should exists on our store). 

Finally, this `withStoreType<S[key]> & HocFunc<S>` is a [Intersection type](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types). According to TypeScript documentation "An intersection type combines multiple types into one". So if `S[key]` extends `initialStore`, we set the intersection type, if not, the hook/hoc type only.

### createStore
Last, the function to export from `Teaful`, the masterpiece:

```javascript
function createStore<S extends initialStoreType>(
    initial?: S,
    afterCallback?: afterCallbackType<S>
  ): {
    getStore: HookDry<S> & getStoreType<S>;
    useStore: Hook<S> & useStoreType<S>;
    withStore: HocFunc<S> & withStoreType<S>;
  };
```

## Conclusions

That's definitely not everything, but there are few steps that you'll face:

1. Check how to stop auto-generated types, check if types are generated by the bundler like our case, by `tsconfig.json` or whatever.
2. Create a custom types on a `d.ts` file.
3. Indicate to `package.json` the place of that file with property `"types"`.

Adding custom types to a javascript library could be difficult at the beginning, but will improve the dev-experience from your users. 

And most important, this could be a great opportunity to learn and improve your skills, to start networking with the community or a good way to help other devs.

I hope it was helpful to you, have a super nice day!

Thank to [english.with.yama@gmail.com](https://www.instagram.com/english.with.yama/) for proofreading the article.