npm package: @hilma/tools
Description: Tools is a package that has some useful client side tools. It makes writing client side code (particularly React boilerplate) easier and simpler. You can see more code examples in [the CodeSandbox](https://codesandbox.io/p/github/ej-shafran/hilma-tools-sa
Author: Yona Ben-Reuven
Latest version: 1.1.0

# Tools

Tools is a package that has some useful client side tools. It makes writing client side code (particularly React boilerplate) easier and simpler. You can see more code examples in [the CodeSandbox](https://codesandbox.io/p/github/ej-shafran/hilma-tools-sandbox/draft/late-dew?workspaceId=a0b9b70f-df77-4288-bcf0-323a0ece81e3).

This documentation is also available in [the Hilma Confluence space](https://hilma.atlassian.net/wiki/spaces/TD/pages/698515457/Hilma+Tools).

- [Tools](#tools)
    - [Installation](#installation)
    - [Usage](#usage)
	    - [`provide` and `wrap`](#provide-and-wrap)
	    - [`withContext`](#withcontext)
	    - [`createContextHook`](#createcontexthook)
	    - [`createMobXContext`](#createmobxcontext)
	    - [`useAsyncState`](#useasyncstate)
	    - [`useAsyncEffect`](#useasynceffect)
        - [`useLocalStorage` and `useSessionStorage`](#uselocalstorage-and-usesessionstorage)
        - [`ErrorBoundary`](#errorboundary)
	    - [`isCapacitor`](#iscapacitor)
	    - [`getDisplayName`](#getdisplayname)
    -  [API](#api)
	    - [`provide`](#provide)
            - [`wrap`](#wrap)
	    - [`withContext`](#withcontext-1)
	    - [`createContextHook`](#createcontexthook-1)
	    - [`createMobXContext`](#createmobxcontext-1)
	    - [`useAsyncState`](#useasyncstate-1)
	    - [`useAsyncEffect`](#useasynceffect-1)
        - [`useLocalStorage`](#uselocalstorage)
        - [`useSessionStorage`](#usesessionstorage)
        - [`ErrorBoundary`](#errorboundary)
	    - [`isCapacitor`](#iscapacitor-1)
	    - [`getDisplayName`](#getdisplayname-1)

## Installation

``` bash
npm install @hilma/tools
```

## Usage

### `provide` (and `wrap`)

`provide` is a function that is used to eliminate nested providers in React code.

Often we have a component with tons of wrappers or providers, where all we care about is the component logic itself, like this:

```tsx
import React from 'react';

const App = () => {
    return (
        <AuthProvider>
            <ThemeProvider>
                <StylesProvider>
                    <div className="App">
                        {/* .... */}
                    </div>
                </StylesProvider>
            </ThemeProvider>
        </AuthProvider>
    );
}

export default App;
```

Not even taking into account that `App` might want to use the context provided by `AuthProvider`, `ThemeProvider`, and the like, this code is already looking much more complicated than it should be. `provide` aims to simplify code like this.

__With `provide`:__

```tsx
import React from 'react';
import { provide } from '@hilma/tools';

const App = () => {
    return (
        <div className="App">
            {/* .... */}
        </div>
    );
}

export default provide(AuthProvider, ThemeProvider, StylesProvider)(App);
```

This allows us to access the context provided by `AuthProvider`, `ThemeProvider`, etc. from inside of `App`, and makes our code much simpler to read and understand.

If you want to pass props to a provider, you can use a tuple where the first item is the provider and the second is the props:

```tsx
import React from 'react';
import { provide } from '@hilma/tools';

const App = () => {
    return (
        <div className="App">
            {/* .... */}
        </div>
    );
}

export default provide(
    [AuthProvider, { basename: "/" }],
    ThemeProvider,
    [StylesProvider, { style: "dark" }]
)(App);
```

### `withContext`

`withContext` is used to consume multiple contexts via props in some component.

__Example__:

```tsx
import React, { createContext } from 'react';
import { withContext } from '@hilma/tools';

const ThemeContext = createContext("black");
const ApiContext = createContext("http://localhost:8080"):

const MyComponent: React.FC<{ theme: string; api: string; }> = (props) => {
	// instead of doing
	// const theme = useContext(ThemeContext);
	// const api = useContext(ApiContext);
	// we can just get the context values from the props
	const { theme, api } = props;

	return (
		<div>
			{/* ... */}
		</div>
	);
}

const mapContextToProps = {
    theme: ThemeContext,
    api: ApiContext
}

export default withContext(mapContextToProps)(MyComponent);
```

Our component here takes a `theme` and `api` prop that correlate to the values provided by `ThemeContext` and `ApiContext`. We then define a `mapContextToProps` object that connects the contexts to the props.

### `createContextHook`

We often can't populate our React contexts with values when we're creating them. A common solution to this problem is to define our context's type with `SomeType | null`, like this:

```tsx
const UsernameContext = createContext<string | null>(null);
```

And then building our own custom `useMyContext` hook that can ignore this `null`, like so:

```tsx
// either
const useMyContext = () => useContext(UsernameContext)!;

// or
const useMyContext = () => {
	const value = useContext(UsernameContext);
	if (value === null) throw new Error("You called useUsername while not inside of the UsernameProvider");
	return value;
}
```

`createContextHook` simplifies this process by automating the second approach. So the second version of `useMyContext` in the example above is the same as doing:

```tsx
import { createContextHook } from '@hilma/tools';

UsernameContext.displayName = "Username";
const useMyContext = createContextHook(UsernameContext);
```

### `createMobXContext`

`createMobXContext` is a function that eliminates the boilerplate needed to use `mobx` with `React`. It works with `createContextHook`, and is based on the fact that when using `mobx`, we _do_ have a starting value (the store itself).

```tsx
import { makeAutoObservable } from 'mobx';

class ThemeStore {
    color = "dark";

    constructor() {
        makeAutoObservable(this);
    }

    setColor = color => {
        this.color = color;
    }
}

const theme = new ThemeStore();

export const [ThemeContext, ThemeProvider, useTheme] = createMobXContext(theme);
```

Here we pass to  `createMobXContext` our store instance and get back a tuple with three items: 
1) A context for the `ThemeStore`
2) A provider that we can wrap our application with
3) A hook that uses that context

Let's look at an example of using these items:

```tsx
import { ThemeContext, ThemeProvider, useTheme } from './ThemeContext';

const App = () => (
    <ThemeProvider>
        <FuncComp />
        <ClassComp />
    </ThemeProvider>
);

export default App;

const FuncComp = () => {
    const theme = useTheme();
    
    return (
        <div>{theme.color}</div>
    );
}

class ClassComp {
    static contextType = ThemeContext;
    
    render() {
        return (
            <div>{this.context.color}</div>
        );
    }
}
```

### `useAsyncState`

`useAsyncState` is a hook based on `useState` but with some extra useful asynchronous functionality. It returns a tuple with three items: 
1) `state`,  the actual state, just like with `useState`
2) `setState`, which sets the value of the state and returns a promise with the new state
3) `getState`, which returns a promise of the state's current value no matter when. This is needed because the `state` variable is not always up to date with the latest state inside of a `useEffect`.

```tsx
import { useAsyncState } from '@hilma/tools';

const Comp = () => {
    const [color, setColor, getColor] = useAsyncState('black');
    
    const updateColorWithAwait = async (event) => {
        const newColor = await setColor(event.target.value);
        console.log(newColor);
    }

    const updateColorWithCallback = (event) => {
        setColor(event.target.value, newColor => {
            console.log(newColor);
        });
    }

    const getColorAsync = async () => {
        const color = await getColor();
        console.log(color);
    }
    
    return <div></div>;
}
```

### `useAsyncEffect`

`useAsyncEffect` is a hook based on the `useEffect` hook but with some extra useful asynchronous functionality. By default, `useEffect` doesn't accept an `async` function because the function returns a promise. It also doesn't accept an `async` cleanup function. The `useAsyncEffect`  allows you to do both:

```tsx
import { useAsyncEffect } from '@hilma/tools';

const Comp = () => {

    useAsyncEffect(async () => {
        // stuff

        return async () => {
            // more stuff
        }
    }, []);

    return <div></div>;
}
```

You don't have to call `useAsyncEffect` with an `async` function or with an `async` cleanup function.

Because the behavior of the cleanup can be asynchronous, the cleanup might be called after the next effect (if you don't have an empty dependency array) or after the component has unmounted.

To make sure you're not updating state on an unmounted component, or updating state after the next effect has run, you can use the `isMounted` parameter which is passed into the callback:

```tsx
import { useAsyncEffect } from '@hilma/tools';

const Comp = () => {
    // ...

    useAsyncEffect(async (isMounted) => {
        await someActionThatTakesAWhile();
        if (isMounted.current) setSomeState();
    }, [])

    return <div></div>;
}
```

### `useLocalStorage` and `useSessionStorage`

These functions wrap `useState` and create a simple API for working with JSON data stored in either `localStorage` or `sessionStorage`.

```tsx
const MyComponent = () => {
    // the first parameter is the key to store the data in
    // the second parameter is the value to default to, in case the data
    // doesn't yet exist or is corrupted somehow
    const [theme, setTheme] = useLocalStorage<"dark" | "light">("theme", "dark");
    const [canSendHeart, setCanSendHeart] = useSessionStorage("has-seen-popup", true);

    return (
        <div className={theme}>
            <button 
                onClick={() => {
                    // this will update both the state (causing a re-render) and `localStorage`
                    setTheme("light");
                }}
            >
                Change Theme
            </button>

            {canSendHeart && (
                <button 
                    onClick={() => {
                        // this will update both the state (causing a re-render) and `sessionStorage`
                        setCanSendHeart(false);
                    }}
                >
                    Send Heart
                </button>
            )}
        </div>
    );
}
```

### `ErrorBoundary`

A component which can be used like a `catch` block for handling uncaught errors anywhere within it.

__Note:__ React will still print console warnings when uncaught errors are thrown inside of an application. We recommend handling scenarios that are likely to produce errors more explicitly, and using `ErrorBoundary` to handle severe edge cases.

```tsx
const Throws: React.FC = () => {
  throw new Error("ERROR");
}

const App: React.FC = () => {
    return (
        <ErrorBoundary
          fallback={<div>An Error Has Occured!</div>}
          callback={(error, info) => { ... }}
        >
          <Throws />
        </ErrorBoundary>
    );
}
```

### `isCapacitor`

Returns a boolean value. If `true`, the code is running in a `Capacitor` environment.

```tsx
import { isCapacitor } from '@hilma/tools';

console.log(isCapacitor());
```

### `getDisplayName`

A function that accepts a `React` component and returns its name. (This is mainly for testing and better error messages for developers; don't rely on this function in production).

```tsx
import { getDisplayName } from '@hilma/tools';

const Comp = () => {
    return <div></div>;
}

console.log(getDisplayName(Comp)); // 'Comp'

const OtherComp = () => {
	return <div></div>;
}

OtherComp.displayName = 'MyComponent';

console.log(getDisplayName(OtherComp)); // 'MyComponent';
```

## API

### `provide`

```typescript
export function provide<TParents extends { [key: string]: any }[]>(
	...parents: Providers<TParents>
): <TProps>(
	child: React.ComponentType<TProps>
) => React.ComponentType<TProps>;
```

The `Providers` type maps an array of prop types to an array of either `ComponentType` or `[ComponentType, Props]`, depending on which props are required and whether the props include `children`.

- If a provider does not take the `children` prop, it cannot be used within `parents`
- If a provider doesn't take any required props, it can either be passed as `Component` or as `[Component, Props]`
- If a provider takes any required props, it _must_ be passed as `[Component, Props]`

### `wrap`

```typescript
export function wrap<TParents extends { [key: string]: any }[]>(
	...parents: Provider<TParents>
): {
	(element: Element) => Element;
	<TProps>(
		component: React.ComponentType<TProps>, props: TProps
	) => Element;
}
```

The `Providers` type here is the same as for `provide`. See [above](#provide).

### `withContext`

```typescript
export function withContext<T extends {}>(
	mapContextToProps: MapContextToProps<T>
): <TProps extends T>(
	child: React.ComponentType<TProps>
) => React.ComponentType<Omit<TProps, keyof T>>;
```

The `MapContextToProps` type takes a generic type `T` (some basic props object) and returns a type with that type's value, mapped to context types.

###  `createContextHook`

```typescript
export function createContextHook<T>(
	context: React.Context<T | null>
): () => T
```

### `createMobXContext`

```typescript
export function createMobXContext<T extends { [key: string]: any }>(
	storeInstance: T
): [React.Context<T>, React.FC<{ children?: React.ReactNode }>, () => T]
```

###  `useAsyncState`

```typescript
export function useAsyncState<T>(
	initialState: T | (() => T)
): [T, (value: T | ((prev: T) => T)) => Promise<T>, () => Promise<T>]
```

### `useAsyncEffect`

```typescript
export function useAsyncEffect(
	effect: (isMounted: { current: boolean }) => (void | (() => void) | Promise<void | (() => void)>),
	deps: React.DependencyList
): void;
```

### `useLocalStorage`

```typescript
export function useLocalStorage<T>(
    key: string,
    fallback: T
): [T, (value: T | ((prev: T) => T)) => void];
```

### `useSessionStorage`

```typescript
export function useSessionStorage<T>(
    key: string,
    fallback: T
): [T, (value: T | ((prev: T) => T)) => void];
```

### `ErrorBoundary`

```typescript
export interface ErrorBoundaryProps {
   children?: React.ReactNode; 
   fallback?: React.ReactNode;
   onError?: (error: unknown, info: React.ErrorInfo) => void;
};

export class ErrorBoundary extends React.Component<ErrorBoundaryProps> { ... };
```

### `isCapacitor`

```typescript
export function isCapacitor(): boolean
```

### `getDisplayName`

```typescript
export function getDisplayName(
	component: React.ComponentType<unknown>
): string;
```