Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/dilan-dio4/use-safe-async-mount

React & React Native hook for mounting asynchronous components with type-safe definitions.
https://github.com/dilan-dio4/use-safe-async-mount

expo express hooks nextjs nodejs npm pnpm react react-hooks react-native reactjs typescript vite vscode webpack

Last synced: 2 months ago
JSON representation

React & React Native hook for mounting asynchronous components with type-safe definitions.

Awesome Lists containing this project

README

        


use-safe-async-mount
The Asynchronous Functional Component Mounter


## The Problem

This is a `useEffect` hook with zero dependencies:

```js
useEffect(() => {
// Code that runs "on mount" (unfortunately)
}, [])
```

It is the most incorrectly [_and dangerously_] used part of the react functional component lifecycle.

Whenever using this hook (_with the empty dependency array_), you should ask yourself two questions:

1. Am I using this to **just** initialize a variable based on some synchronously computed value?
* ```useEffect(() => { setValue(computeMyValue()) }, [])```

2. Am I using this to **just** conditionally initialize a state variable based on props?
* ```useEffect(() => { setValue(someProp ? "a" : "b") }, [])```

If either of these situations describes you, there's a high chance you should be just be computing the value in-line or using `useMemo`. This **saves** you from having to deal with the initial render when your value (`useState`, `useRef`, `var`, etc.) is _undefined_.

Here's some situations that **do** fit in the empty `useEffect` hook:

* Making a network request
* Setting/removing an event listener
* UI-related analytics tracking

## Async State

When a component's state depends on a value gathered from an async function, the common solution is to manually invoke it directly from the empty `useEffect` hook:

### The Traditional Pattern

```js
function ExampleComponent() {
const [stateOne, setStateOne] = useState()

useEffect(() => {
someAsyncFunction().then(res => setStateOne(res))
// OR
const run = async () => {
const res = await someAsyncFunction()
setStateOne(res)
}
run();
}, [])

return (
<>

My Example Component


{stateOne &&

{stateOne}

}
>
)
}
```

## The Drawbacks

There's three negative implications to this solution:

1. When the component initially mounts, the variable is _undefined_. This requires more render logic.
2. The component may unmount during the asynchronous request. Setting a state variable on an unmounted component is a **memory leak and will throw an error**.
3. In TypeScript projects, your compiler won't recognize that your variable has been defined. That means `!`'s everywhere.

## The Solution

### Installation

```bash
npm i use-safe-async-mount
```

`use-safe-async-mount` solves these problems by acting as a **true hook-based, type-safe `componentWillMount`** implementation.

### Example

```js
import useSafeAsyncMount from 'use-safe-async-mount';

function ExampleComponent() {

const { SafeRender } = useSafeAsyncMount(async isActive => {
const res = await someAsyncFunction()
if (isActive()) {
// ^ This avoids setting component state after unmount

// These values are defined and type-safe in the `SafeRender` component
return {
stateOne: "Some value my component depends on",
stateTwo: res
}
}
})

return (
<>

My Example Component



{({ stateOne, stateTwo }) => (

{stateOne}


)}

>
)
}
```

## Example

Here's an [interactive example](https://dilan-dio4.github.io/use-safe-async-mount/test/) and the [associated source code](/test/src/App.tsx).

## Inspirations

* [use-async-effect](https://github.com/rauldeheer/use-async-effect)