• Stars
    star
    195
  • Rank 198,877 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 5 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

React hook for async effects powered by generator functions.

useAsyncEffect

npm npm bundle size Dependencies NPM CircleCI semantic-release

Simple type-safe async effects for React powered by generator functions.

import React from "react";
import useAsyncEffect from "@n1ru4l/use-async-effect";

const MyComponent = ({ filter }) => {
  const [data, setData] = React.useState(null);

  useAsyncEffect(
    function* (onCancel, c) {
      const controller = new AbortController();

      onCancel(() => controller.abort());

      const data = yield* c(
        fetch("/data?filter=" + filter, {
          signal: controller.signal,
        }).then((res) => res.json())
      );

      setData(data);
    },
    [filter]
  );

  return data ? <RenderData data={data} /> : null;
};

Install Instructions

yarn add -E @n1ru4l/use-async-effect

or

npm install -E @n1ru4l/use-async-effect

The problem

Doing async stuff with useEffect clutters your code:

  • ๐Ÿ˜– You cannot pass an async function to useEffect
  • ๐Ÿคข You cannot cancel an async function
  • ๐Ÿคฎ You have to manually keep track whether you can set state or not

This micro library tries to solve this issue by using generator functions:

  • โœ… Pass a generator to useAsyncEffect
  • โœ… Return cleanup function from generator function
  • โœ… Automatically stop running the generator after the dependency list has changed or the component did unmount
  • โœ… Optional cancelation handling via events e.g. for canceling your fetch request with AbortController

Example

Before ๐Ÿ˜–

import React, { useEffect } from "react";

const MyComponent = ({ filter }) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isCanceled = false;
    const controller = new AbortController();

    const runHandler = async () => {
      try {
        const data = await fetch("/data?filter=" + filter, {
          signal: controller.signal,
        }).then((res) => res.json());
        if (isCanceled) {
          return;
        }
        setData(data);
      } catch (err) {}
    };

    runHandler();
    return () => {
      isCanceled = true;
      controller.abort();
    };
  }, [filter]);

  return data ? <RenderData data={data} /> : null;
};

After ๐Ÿคฉ

import React from "react";
import useAsyncEffect from "@n1ru4l/use-async-effect";

const MyComponent = ({ filter }) => {
  const [data, setData] = useState(null);

  useAsyncEffect(
    function* (onCancel, c) {
      const controller = new AbortController();

      onCancel(() => controller.abort());

      const data = yield* c(
        fetch("/data?filter=" + filter, {
          signal: controller.signal,
        }).then((res) => res.json())
      );

      setData(data);
    },
    [filter]
  );

  return data ? <RenderData data={data} /> : null;
};

Usage

Works like useEffect, but with a generator function.

Basic Usage

import React, { useState } from "react";
import useAsyncEffect from "@n1ru4l/use-async-effect";

const MyDoggoImage = () => {
  const [doggoImageSrc, setDoggoImageSrc] = useState(null);
  useAsyncEffect(function* (_, c) {
    const { message } = yield* c(
      fetch("https://dog.ceo/api/breeds/image/random").then((res) => res.json())
    );
    setDoggoImageSrc(message);
  }, []);

  return doggoImageSrc ? <img src={doggoImageSrc} /> : null;
};

Edit use-async-effect doggo demo

Cancel handler (Cancelling an in-flight fetch request)

You can react to cancels, that might occur while a promise has not resolved yet, by registering a handler via onCancel. After an async operation has been processed, the onCancel handler is automatically being unset.

import React, { useState } from "react";
import useAsyncEffect from "@n1ru4l/use-async-effect";

const MyDoggoImage = () => {
  const [doggoImageSrc, setDoggoImageSrc] = useState(null);
  useAsyncEffect(function* (onCancel, c) {
    const abortController = new AbortController();
    onCancel(() => abortController.abort());
    const { message } = yield c(
      fetch("https://dog.ceo/api/breeds/image/random", {
        signal: abortController.signal,
      }).then((res) => res.json())
    );
    setDoggoImageSrc(message);
  }, []);

  return doggoImageSrc ? <img src={doggoImageSrc} /> : null;
};

Edit use-async-effect doggo cancel demo

Cleanup Handler

Similar to React.useEffect you can return a cleanup function from your generator function. It will be called once the effect dependencies change or the component is unmounted. Please take note that the whole generator must be executed before the cleanup handler can be invoked. In case you setup event listeners etc. earlier you will also have to clean them up by specifiying a cancel handler.

import React, { useState } from "react";
import useAsyncEffect from "@n1ru4l/use-async-effect";

const MyDoggoImage = () => {
  const [doggoImageSrc, setDoggoImageSrc] = useState(null);
  useAsyncEffect(function* (_, c) {
    const { message } = yield* c(
      fetch("https://dog.ceo/api/breeds/image/random").then((res) => res.json())
    );
    setDoggoImageSrc(message);

    const listener = () => {
      console.log("I LOVE DOGGIES", message);
    };
    window.addEventListener("mousemove", listener);
    return () => window.removeEventListener("mousemove", listener);
  }, []);

  return doggoImageSrc ? <img src={doggoImageSrc} /> : null;
};

Edit use-async-effect cleanup doggo demo

Setup eslint for eslint-plugin-react-hooks

You need to configure the react-hooks/exhaustive-deps plugin to treat useAsyncEffect as a hook with dependencies.

Add the following to your eslint config file:

{
  "rules": {
    "react-hooks/exhaustive-deps": [
      "warn",
      {
        "additionalHooks": "useAsyncEffect"
      }
    ]
  }
}

TypeScript

We expose a helper function for TypeScript that allows interferring the correct Promise resolve type. It uses some type-casting magic under the hood and requires you to use the yield* keyword instead of the yield keyword.

useAsyncEffect(function* (setErrorHandler, c) {
  const numericValue = yield* c(Promise.resolve(123));
  // type of numericValue is number ๐ŸŽ‰
});

API

useAsyncEffect Hook

Runs a effect that includes async operations. The effect ins cancelled upon dependency change/unmount.

function useAsyncEffect(
  createGenerator: (
    setCancelHandler: (
      onCancel?: null | (() => void),
      onCancelError?: null | ((err: Error) => void)
    ) => void,
    cast: <T>(promise: Promise<T>) => Generator<Promise<T>, T>
  ) => Iterator<any, any, any>,
  deps?: React.DependencyList
): void;

Contributing

Please check our contribution guides Contributing.

LICENSE

MIT.

More Repositories

1

envelop

Envelop is a lightweight library allowing developers to easily develop, share, collaborate and extend their GraphQL execution layer. Envelop is the missing GraphQL plugin system.
TypeScript
785
star
2

graphql-live-query

Realtime GraphQL Live Queries with JavaScript
TypeScript
436
star
3

graphql-bleeding-edge-playground

Demonstration of queries/mutations/defer/stream/subscriptions/live
TypeScript
137
star
4

graphql-schema-generator-rest

Generate your GraphQL schema from type definitions
JavaScript
97
star
5

graphiql-apollo-tracing

poc graphiql with apollo-tracing support
JavaScript
51
star
6

graphql-public-schema-filter

Filter your GraphQL graph into a subgraph. Code-first & SDL-first!
TypeScript
51
star
7

graphql-codegen-relay-plugins

Use the power of the relay-compiler with every GraphQL project!
TypeScript
39
star
8

relay-compiler-repl

Convince your team that you should use the relay-compiler ๐Ÿ˜‰
TypeScript
34
star
9

graphql-live-chat

Chat built with GraphQL Live Subscriptions.
TypeScript
28
star
10

push-pull-async-iterable-iterator

Create an AsyncIterableIterator from anything while handling back-pressure!
TypeScript
23
star
11

character-overlay

Web App for adding an OBS overlay with character information such as name, picture, and health for your favorite role-playing game.
TypeScript
22
star
12

react-in-center-of-screen

Determine if a list item is in the center of your viewport https://codesandbox.io/s/1vw1q2288q
JavaScript
14
star
13

react-use-transition

TypeScript
14
star
14

ember-cli-css-preprocess

Process your stylesheets using various preprocessors like Sass or PostCSS. Looking for new maintainers!
JavaScript
13
star
15

vue-component-database

An online code editor for Vue Single File Components
Vue
11
star
16

bundle-anywhere

TypeScript
10
star
17

toposort

TypeScript toposort
TypeScript
8
star
18

cv-image-grid-detection

JavaScript
7
star
19

ember-card-stacks

Card Stacks inspired by https://tympanus.net/Development/CardStackEffects/
JavaScript
6
star
20

hive-workshop

GraphQL Hive Workshop Material
TypeScript
6
star
21

graphql-operation-merger

A tiny opinionated graphql operation merger
TypeScript
5
star
22

dsa-wiki-crawler

TypeScript
4
star
23

react-time-ago

Display formatted date strings in real-time
JavaScript
4
star
24

pokemon-tcg-deck-scraper-api

JavaScript
4
star
25

ssm-parameter-env

Supply your environment with the AWS Systems Manager Parameter Store
TypeScript
4
star
26

manga-exporter

Collection of utilities for extracting unlicensed community translated mangas from websites into eReader (Kindle) friendly format.
TypeScript
4
star
27

envelop-demo

TypeScript
3
star
28

psql-import-action

Github Action for importing a Postgres Database Dump
Shell
3
star
29

slonik-utilities

Utilities for the PostgreSQL Client Slonik
TypeScript
3
star
30

yoga-grats-demo

TypeScript
2
star
31

docker-image-node-10-with-docker-and-compose

Made for CI
Shell
1
star
32

pg-advisary-lock-check

TypeScript
1
star
33

serverless-codepipeline-slack-notifier

Publish Codepipeline Status Updates to a Slack Channel
JavaScript
1
star
34

n1ru4l

1
star
35

spotify-player-android-sdk

Shell
1
star
36

ember-cli-postcss-fixed

DEPRECATED PLEASE USE ember-cli-css-preprocess
JavaScript
1
star
37

my-library-boilerplate

i keep copy pasting this...
JavaScript
1
star
38

react-native-video-compress-example

Objective-C
1
star
39

graphql-tools-commonjs-import-repro

https://github.com/ardatan/graphql-tools/discussions/4581#discussioncomment-3329673
JavaScript
1
star
40

hive-github-workflow-example

I use this for testing graphql-hive GitHub integrations :)
1
star
41

temperature_converter

Rust
1
star
42

yet-another-react-boilerplate

๐Ÿš€ Universal React Boilerplate. Using React Router, React Helmet and Apollo Client
JavaScript
1
star
43

node-sqlite3

Asynchronous, non-blocking SQLite3 bindings for Node.js
PLpgSQL
1
star
44

docker-python-node-audio-processing

Dockerfile
1
star
45

bob

Build tool used in libraries maintained by The Guild
JavaScript
1
star