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

https://github.com/mittwald/react-use-promise

Simple and declarative use of Promises in your React components. Observe their state and refresh them in various advanced ways.
https://github.com/mittwald/react-use-promise

api async data fetch hooks promise react suspense

Last synced: about 1 month ago
JSON representation

Simple and declarative use of Promises in your React components. Observe their state and refresh them in various advanced ways.

Awesome Lists containing this project

README

        

# React Use Promise

Simple and declarative use of Promises in your React components. Observe their
state and refresh them in various advanced ways.

**Now with built-in support for ๐ŸŒ [HTTP](#-http-resources)!**

```jsx
import { Suspense } from "react";
import { usePromise, refresh } from "@mittwald/react-use-promise";

// Async loader ๐Ÿ‘‡ function
const loadNewsItem = async (id) => {
const res = await fetch(
`https://hacker-news.firebaseio.com/v0/item/${id}.json`,
);
return res.json();
};

const NewsItem = ({ id }) => {
const news = usePromise(loadNewsItem, [id], {
// โœจ Use the async loader ๐Ÿ‘† function with its ID-parameter
tags: [`news/${id}`],
// Use tags ๐Ÿท๏ธ with support for "tree structures" ๐ŸŒณ
});

// Do not care about any loading states โ€“ just use the result ๐Ÿคฉ
return (


  • {news.by}: {news.title}

  • );
    };

    const App = () => {
    const reloadAllNews = () => {
    // โœจ Reload Promises ๐Ÿ”„ by using tags and glob pattern *๏ธโƒฃ
    refresh({ tag: "news/*" });
    };

    return (
    // The Suspense component will render a fallback๏ธ
    // if any child component is in loading state โฑ๏ธ
    Loading...}>





    Reload news

    );
    };
    ```

    ## Features

    - Simple and declarative use of Promises, [loading-](#defining-loading-views)
    and [error views](#error-handling)
    - Built-in support to load data with [HTTP resources](#-http-resources)
    - Works with data fetching via Next.js server actions
    - Auto-refresh after [timeout](#autorefresh-only-supported-by-usepromise) or on
    [window focus](#refreshonwindowfocus-only-supported-by-usepromise)
    - Type-Safe API
    - No "double-loading" when using "same" Promise in different places in your app
    - Caching with support for [custom tags](#tags-1)
    - [Opt-out Suspense-based loading](#opt-out-suspense)
    - [Cache invalidation](#refreshing-resources) with
    [glob support](#hierarchical-tags)
    - [Observable loading state](#watchstate)
    - Set up [lazy-loading](#-lazy-loading-with-async-resources) "async resources"
    with the alternative [`getAsyncResource`-API](#getasyncresource) and pass them
    as prop to child components
    - ["Resourceify"](#resourceify) any async function
    - Fully tested and well-structured TypeScript code-base

    ## Table of Contents

    - [Installing](#installing)
    - [Terminology](#terminology)
    - [API](#api)
    - [usePromise](#usepromise)
    - [getAsyncResource](#getasyncresource)
    - [Async resource](#async-resource-1)
    - [Options](#options)
    - [refresh](#refresh-1)
    - [Caching](#caching)
    - [Challenges concerning Caching](#challenges-concerning-caching)
    - [Resource store](#resource-store)
    - [Creating unique storage keys](#creating-unique-storage-keys)
    - [Using explicit loader IDs](#using-explicit-loader-ids)
    - [Tags](#tags-1)
    - [Hierarchical Tags](#hierarchical-tags)
    - [Refreshing resources](#refreshing-resources)
    - [HTTP Resources](#-http-resources)
    - [Request config](#request-config)
    - [Default request config](#default-request-config)
    - [Caching and refreshing of HTTP Resources](#caching-and-refreshing-of-http-resources)
    - [API](#http-api)
    - [Lazy loading with Async Resources](#-lazy-loading-with-async-resources)
    - [Defining loading views](#defining-loading-views)
    - [Opt-Out Suspense](#opt-out-suspense)
    - [Opt-Out Loading](#opt-out-loading)
    - [Error handling](#error-handling)
    - [Best practices](#best-practices)
    - [Migration guides](#migration-guides)

    ## Installing

    With npm:

    ```shell
    $ npm install @mittwald/react-use-promise
    ```

    With Yarn:

    ```shell
    $ yarn add @mittwald/react-use-promise
    ```

    ## Terminology

    ### (Async) resource

    An async resource (or just resource) represents something that has to be loaded
    asynchronously. Asynchronous loading involves different states, like "loading"
    or "loading is done", that should be reflected in the UI. Basically async
    resources encapsulating Promises and equipping them with relevant features like
    observing the Promises state or caching the result.

    ### Async loader (function)

    The async loader function is simply a function returning a Promise and thus acts
    as the basic input for async resources.

    ## API

    ### usePromise

    Get the result of a Promise by passing an async loader with the relevant loader
    parameters and an optional configuration to the `usePromise` hook.

    #### usePromise(asyncLoader, loaderParameters, options?)

    Returns: the value of the async resource

    For possible options see [Options section](#options).

    ```javascript
    import { usePromise } from "@mittwald/react-use-promise";

    // 1. Inline loader function
    const response = usePromise(() => axios("/user/12345"), []);

    // 2. Loader function with parameters
    const getUser = (id) => axios(`/user/${id}`);
    const response = usePromise(getUser, [12345]); // ๐Ÿ‘ˆ loader parameters as array

    // 3. With options
    const response = usePromise(getUser, [12345], {
    autoRefresh: {
    seconds: 30,
    },
    });
    ```

    ### getAsyncResource

    This is an alternative and more advanced API to the `usePromise` hook. For
    details see
    [Lazy Loading with Async Resources](#-lazy-loading-with-async-resources). The
    `usePromise` hook uses the [`getAsyncResource`-API](#getasyncresource) under the
    hood and is basically just a shortcut for
    `getAsyncResource(asyncLoader, loaderParameters, options).use()`.

    Get an async resource by passing an async loader with the relevant loader
    parameters and an optional configuration to the `getAsyncResource` function.

    #### getAsyncResource(asyncLoader, loaderParameters, options?)

    Returns: [Async resource](#async-resource-1)

    For possible options see [Options section](#options).

    ```javascript
    import { getAsyncResource } from "@mittwald/react-use-promise";

    // 1. Inline loader function
    const userResource = getAsyncResource(() => axios(`/user/123`), []);

    // 2. Loader function with parameters
    const getUser = (id) => axios(`/user/${id}`);
    const userResource = getAsyncResource(getUser, [123]);

    // 3. With options
    const userResource = getAsyncResource(getUser, [123], {
    tags: ["api"],
    });

    // 4. Usage in factory function
    const getUserResource = (id) => getAsyncResource(getUser, [id]);
    ```

    ### Async resource

    The async resource returned by `getAsyncResource` has the following API.

    #### .use(options?)

    For possible options see [Options section](#options).

    Returns: the value of the async resource, or the result object when
    `useSuspense: false` (see [Opt-out Suspense](#opt-out-suspense))

    Calling the `use` method will actually start the loading process and returns the
    value of the async resource, once it is loaded. Everytime the resource is
    refreshed, the used value automatically updates itself.

    ```jsx
    import getUserResource from "../resources/user";

    const Username = ({ id }) => {
    const user = getUserResource(id).use(); // ๐Ÿ‘ˆ using the resource value
    return <>{user.name}>;
    };
    ```

    #### .refresh()

    Calling the `refresh` method will clear the cached resource value and trigger a
    reload, if the resource is being used in any mounted component.

    ```jsx
    import getScoreResource from "../resources/score";

    const Score = ({ matchId }) => {
    const scoreResource = getScoreResource(matchId);
    const score = scoreResource.use();
    const reloadScore = () => scoreResource.refresh(); // ๐Ÿ‘ˆ refresh the resource

    return (


    {score.home} - {score.guest}

    Reload

    );
    };
    ```

    #### .watchState()

    Returns: `"void" | "loading" | "loaded" | "error"`

    You can watch the resources state by calling the `watchState` method.

    ```jsx
    import getScoreResource from "../resources/score";

    const Score = ({ matchId }) => {
    const scoreResource = getScoreResource(matchId);
    const scoreResourceState = scoreResource.watchState();
    const score = scoreResource.use();
    const scoreIsLoading = scoreResourceState === "loading";

    return (


    {score.home} - {score.guest}



    );
    };
    ```

    ### Options

    You can configure `usePromise` and `getAsyncResource` with the following
    options:

    #### autoRefresh โ€“ only supported by hooks (`use*`)

    Type:
    [Duration like object](https://moment.github.io/luxon/api-docs/index.html#durationfromobject)\
    Default:
    `undefined`

    When a duration is configured, the resource will automatically be refreshed in
    the provided interval. If the same resource has multiple auto-refresh intervals,
    the shortest interval will be used.

    ```javascript
    autoRefresh: {
    seconds: 30;
    }
    ```

    #### refreshOnWindowFocus โ€“ only supported by hooks (`use*`)

    Type: `boolean`\
    Default: `false`

    Set this option to `true`, if the resource should automatically be refreshed, if
    the window is re-focused.

    #### keepValueWhileLoading โ€“ only supported by hooks (`use*`)

    Type: `boolean`\
    Default: `true`

    If `true`, the previously loaded value will be returned during refresh is in
    progress. The loading view will only be triggered during initial load.

    If `false`, the loading view will always be triggered โ€“ during initial load and
    refresh as well.

    #### tags

    Type: `string[]`\
    Default: `undefined`

    With this option you can assign tags to resources. Tags allow you to be
    expressive and flexible when accessing resources, e.g. when you need to refresh
    multiple resources at once.

    You can find details about how to use tags in the [Tags section](#tags-1).

    ```javascript
    tags: ["hackernews", "getRequest"];
    ```

    #### loaderId

    Type: `string`\
    Default: `"null"`

    **It is very unlikely** that you ever need to use this option, but to get around
    the "same code" issue (see
    ["Caveats of default storage key generation"](#caveats-of-default-storage-key-generation)),
    you can set an explicit loader ID, that identifies the loader function.

    #### useSuspense โ€“ only supported by hooks (`use*`)

    Type: `boolean`\
    Default: `true`

    Set this to `false` to opt-out Suspense-based loading behavior. See
    [Opt-Out Suspense](#opt-out-suspense) for more details.

    ### refresh

    If you do not have direct access to the resource that should be refreshed, you
    can use the global refresh method.

    #### refresh()

    Refreshes all resources.

    ```jsx
    import { refresh } from "@mittwald/react-use-promise";

    export const Menu = () => (




    refresh()}>Refresh all data

    );
    ```

    #### refresh(options)

    The refresh method takes an option object to do a more selective refresh.

    The following options are supported:

    - `tag` (`string`): Refreshes all resources matching the given tag. Glob pattern
    are supported here. See also the
    [Hierarchical Tags section](#hierarchical-tags).
    - `error` (`true | error`): Set this option to `true`, to refresh all resource
    with errors. Set this option to an error instance, to refresh all resources
    with a matching error. See the [Error handling](#error-handling) section for
    more details.

    ### resourceify

    `resourceify` creates a factory function for async resources based on given
    async loader functions.

    #### resourceify(asyncLoader)

    Example of how to create factory functions for your async loaders:

    ```js
    import { resourceify } from "@mittwald/react-use-promise";

    const getUser = (id) => axios(`/user/${id}`);
    // Creates a function to get user resources
    const getUserResource = resourceify(getUser);
    // `myUser` is an async resource
    const myUser = getUserResource(["me"], { refresh: { seconds: 30 } });
    myUser.use();

    // Factory method for HTTP GET-Requests via Axios
    import axios from "axios";
    export const getHttpResource = resourceify(axios.get);
    ```

    ## Caching

    Caching the result of async loader functions is essential to make this library
    work. Without it, components will end-up in an endless render-loop, since the
    re-render after finished loading will trigger the loader function again.

    To break this loop, the result (and even the error) of the loader function is
    cached inside the resource instance. If a cached result exists, the loader must
    not be called again and the cached value is used instead.

    ### Challenges concerning Caching

    This caching approach comes with two essential issues one has to care about:

    - creating unique cache keys to store results
    - providing flexible ways of cache invalidation

    ### Resource store

    Every time when `usePromise` resp. `getAsyncResource` is called, either a new
    resource is created or an existing resource is taken from the resource store. If
    a resources has loaded once, it exists in the store and contains the cached
    result of the async loader function.

    It is noticeable that not the raw result is cached in some "result cache" โ€“ **it
    is the resource the keeps the cached result which itself is stored in the
    resource store**.

    ### Creating unique storage keys

    Basically you do not have to care about storage keys at all, but there are some
    odd situations where the default storage key generation fails, and produces
    conflicting keys. If so, you probably might not get the expected resource. When
    you are experiencing such issues, you better take a look at this section.

    #### Same resource, same storage key

    When creating storage keys, the same key should be formed for async resources
    considered as "same". An async resource is "the same" compared to another
    resource, if:

    - the loader function is the same
    - the parameters used to load the resource are the same

    Examples:

    ```javascript
    const getUser = (id) => axios(`/user/${id}`);
    const getPost = (id) => axios(`/post/${id}`);

    // Same storage key ๐ŸŽ๐ŸŽ
    const response1 = usePromise(getUser, [12345]);
    const response2 = usePromise(getUser, [12345]);

    // Same storage key ๐Ÿ๐Ÿ
    const response3 = usePromise(() => getUser(12345), []);
    const response4 = usePromise(() => getUser(12345), []);

    // Different storage keys ๐ŸŽ๐Ÿ‹
    const response5 = usePromise(getUser, [12345]);
    const response6 = usePromise(getUser, [56789]);

    // Different storage keys ๐ŸŽ๐Ÿ‰
    const response7 = usePromise(getUser, [12345]);
    const response8 = usePromise(getPost, [12345]);

    // Different storage keys ๐ŸŽ๐ŸŒถ๏ธ
    const response9 = usePromise(() => getUser(12345), []);
    const response10 = usePromise(() => getUser(12344 + 1), []);
    ```

    #### Caveats of default storage key generation

    In some odd situations the default storage key generation fails and may generate
    conflicting keys. To evaluate these situations, you should know some details
    about the default behaviour, which is basically implemented like this:

    ```javascript
    import { hash } from "object-code";

    const storageKeyObject = {
    loaderFunction,
    parameters,
    };

    const storageKey = hash(storageKeyObject);
    ```

    As you can see, the implementation heavily relies on the
    [`object-code`](https://www.npmjs.com/package/object-code#getting-started)
    library, which

    > is a blazing fast hash code generator that supports every possible javascript
    > value.

    When it comes to "hashing" a function (in this case the async loader function),
    `object-code` uses `toString()` to get the function _code_ and uses it for
    further hashing. This is the point where problems may arise. When two function
    using the same code, they must not necessarily behave the same. Think of
    functions using variables from their parent scope (๐Ÿคฏ).

    The following example may result in inconsistencies. (But only if users and
    posts share same the IDs.)

    ```jsx
    // File Username.jsx
    import repo from "./user";

    const loadUserById = (id) => repo.loadById(id);
    // same code ๐Ÿ‘†, but different repo ๐Ÿ˜ง

    export const Username = ({ id }) => {
    const user = usePromise(loadUserById, [id]);
    //๐Ÿ‘† may be a cached post
    return <>{user.name}>;
    };

    // File PostTitle.jsx
    import repo from "./post";

    const loadPostById = (id) => repo.loadById(id);
    // same code ๐Ÿ‘†, but different repo ๐Ÿ˜ง

    export const PostTitle = ({ id }) => {
    const post = usePromise(loadPostById, [id]);
    //๐Ÿ‘† may be a cached user
    return <>{post.title}>;
    };
    ```

    ### Using explicit loader IDs

    **It is very unlikely** that you ever need to use this option, but to get around
    the "same code" issue demonstrated above, you can set explicit loader IDs in the
    options object.

    This example shows how to set explicit loader IDs:

    ```javascript
    // ...
    const post = usePromise(loadPostById, [id], {
    loaderId: "loadPostById", // explicit loader ID ๐Ÿ˜ฎโ€๐Ÿ’จ
    });
    // ...
    ```

    ## Tags

    You can assign tags to resources. A tag is simply a string that classifies the
    resource. In advanced scenarios you might want to assign multiple tags to one
    resource, expressing the resource matches different classifications.

    Using relevant tags creates the possibility to be very expressive and flexible
    when it comes to accessing multiple resources at once, e.g. when you need to
    refresh them.

    For example, you might use tags to refresh some specific resources, when a
    backend event occurs.

    ```jsx
    // File components/Chat.jsx
    import { usePromise } from "@mittwald/react-use-promise";

    const Chat = ({ chatId }) => {
    const messages = usePromise(loadChatMessages, [chatId], {
    tags: [`chat/${chatId}`],
    });

    return (


    {messages.map((msg) => (

    ))}

    );
    };

    // File setup.js
    import { refresh } from "@mittwald/react-use-promise";

    backendEventListener.on("chatUpdated", (chatId) => {
    refresh({
    tag: `chat/${chatId}`,
    });
    });
    ```

    ### Hierarchical Tags

    Tags are supporting a tree structure, compared to paths in filesystems or URLs
    (e.g. `chat/12345/messages`). This creates the possibility to structure your
    resources into hierarchical classes. Combined with
    [glob support](https://www.npmjs.com/package/minimatch) in cache invalidation,
    you can invalidate resources matching a certain level in the tree.

    ```js
    import { refresh } from "@mittwald/react-use-promise";

    refresh({
    tag: `chat/12345/**/*`,
    });
    // Matches everything for the chat 12345
    // - chat/12345/messages/1
    // - chat/12345/messages/2
    // - chat/12345/metaData

    refresh({
    tag: `chat/12345/messages/*`,
    });
    // Matches all messages for the chat 12345
    // - chat/12345/messages/1
    // - chat/12345/messages/2
    ```

    ## Refreshing resources

    If a resource has a cached result, the async loader function must not be called
    again and the cached value is used instead. To invalidate the cached value you
    can call the [`refresh()`](#refresh) method on the resource or use the global
    [`refresh()`](#refresh-1) method. As a result the async loader function will
    automatically be called again and a component update is triggered when the
    result is available.

    Refreshing resources that are not used in any mounted component, will suspend
    the reload until the resource is actually used the next time.

    You might also use the auto-refresh mechanisms
    [after timeout](#autorefresh-only-supported-by-usepromise) or
    [refocus window](#refreshonwindowfocus-only-supported-by-usepromise).

    ```js
    import { refresh } from "@mittwald/react-use-promise";
    ```

    ## ๐ŸŒ HTTP Resources

    A major use case of this library might be to load data from some HTTP-API. This
    is exactly why a preconfigured HTTP resource is included in this package. The
    simplest way is to use the `useHttpData()` method to GET data from a given URL.

    ```jsx
    import { useHttpData } from "@mittwald/react-use-promise/http";

    const NewsItem = ({ id }) => {
    const news = useHttpData(
    `https://hacker-news.firebaseio.com/v0/item/${id}.json`,
    );

    return (


  • {news.by}: {news.title}

  • );
    };
    ```

    **Important**: As this feature requires the `axios` package you need to include
    it in your dependencies!

    ```shell
    yarn add axios
    ```

    ### Request config

    Under the hood the popular HTTP client Axios is used to perform the request. To
    make all [Axios config](https://axios-http.com/docs/req_config) options
    available to you, the complete config object can be passed as the second
    parameter to all HTTP functions.

    ```ts
    const videoGames = useHttpData(`/articles`, {
    params: {
    catagory: "video-games",
    },
    });
    ```

    ### Default request config

    To set the default config for each request (e.g the `baseURL` or an
    `Authorization` header) you can use the
    [`axios.defaults`](https://axios-http.com/docs/config_defaults) object.

    ```js
    import axios from "axios";

    axios.defaults.baseURL = "https://api.foo.org/v2";
    ```

    ### Caching and refreshing of HTTP Resources

    HTTP Resources are cached by the identity of the request config โ€“ meaning,
    different request configs result in different HTTP Resources.

    Cache tags added to HTTP Resources:

    - `http/method/(HTTP METHOD)`
    - `http/uri/(REQUEST URI)`

    This lets you refresh HTTP Resources with a certain path.

    ```js
    import { refresh } from "@mittwald/react-use-promise";

    function refreshArticles() {
    refresh({
    tag: "http/uri/**/articles",
    });
    }
    ```

    ### HTTP API

    ### useHttpData(url, requestConfig?, usePromiseOptions?)

    Fetches data from a given URL with an optional request configuration.

    Returns: the parsed body data of the response

    #### url

    The URL to perform the request on

    #### requestConfig?

    The [Axios request config](https://axios-http.com/docs/req_config)

    #### usePromiseOptions?

    For possible options see [Options section](#options).

    ### useHttp(url, requestConfig?, usePromiseOptions?)

    Get the response from a given URL with an optional request configuration.

    Returns: an [Axios response object](https://axios-http.com/docs/res_schema)

    #### url

    The URL to perform the request on

    #### requestConfig?

    The [Axios request config](https://axios-http.com/docs/req_config)

    #### usePromiseOptions?

    For possible options see [Options section](#options).

    ### getHttpResource(url, requestConfig?, usePromiseOptions?)

    Creates an Async Resource that performs an HTTP request as the loader function.

    Returns: an [Axios response object](https://axios-http.com/docs/res_schema)

    #### url

    The URL to perform the request on

    #### requestConfig?

    The [Axios request config](https://axios-http.com/docs/req_config)

    #### usePromiseOptions?

    For possible options see [Options section](#options).

    ## ๐Ÿ˜ด Lazy loading with Async Resources

    When constructing your React app, you might get to the point, where you must
    decide whether to

    - load something from your API and pass the result into some child component,
    - or the child component should load the data itself by some ID from its props.

    Since loading async resources involves different loading states, it is a good
    advice, to react on these state changes, as deep as possible in the component
    tree, to avoid needless re-renderings of parent components.

    ### ๐Ÿ’… Presentational vs. ๐Ÿ“ฆ Container Components

    The components deep in the tree are usually some small components, like the
    `` component showing an avatar image. These kind of components do have
    a clear focus on the visual representation and should not be polluted by some
    loading state handling. They are often referred to as _Presentational
    Components_. They can be developed as "standalone" and showcased in Storybooks,
    without the need of any API.

    When the time has come to bring your Presentational Components to life, you can
    "wrap" the component in a container that "connects" it with real data. This is
    the job of _Container Components_. They care about loading data and compose
    Presentational Components to display the data.

    Example Presentational Component:

    ```tsx
    // Props needed for presentation
    interface Props {
    imageUrl?: string;
    size?: number;
    }

    const Avatar: FC = ({ imageUrl, size }) => {
    return ;
    };
    ```

    Example Container Component:

    ```tsx
    interface Props {
    // ID is needed to load the user
    userId: string;
    // optional presentational props are possible
    size?: number;
    }

    const UserAvatar: FC = ({ userId, size }) => {
    const profile = usePromise(api.loadUserProfile, [userId]);
    return ;
    };
    ```

    ### Async Resources as prop

    As an alternative to the user ID, you might pass an Async User Resource as prop
    to your Container Component. This makes the component more flexible, because it
    is not tied to the actual loading method.

    For instance if user profiles can be loaded via multiple API methods, the
    `` component can still be used without any changes.

    It is noticeable that creating Async Resources in any parent component does
    **not trigger the loading process**. Therefore, the `.use()` method can be used
    in the child components, where the data is actually needed.

    Alternative Container Component with lazy loading:

    ```tsx
    // ...
    import { AsyncResource, getAsyncResource } from "@mittwald/react-use-promise";

    interface Props {
    // Async User Resource passed as prop
    user: AsyncResource;
    size?: number;
    }

    const UserAvatar: FC = ({ user, size }) => {
    // .use() ๐Ÿ”” triggers loading when needed
    const profile = user.use();
    return ;
    };

    const UserProfileMenu: FC = () => {
    // ๐Ÿ˜ด Creating the Async Resource does not trigger loading
    const myProfile = getAsyncResource(api.loadMyUserProfile, []);

    return (




    {/* menu items*/}

    );
    };
    ```

    ## Defining loading views

    When using `@mittwald/react-use-promise` (or other Suspense-based approaches)
    you have to define a loading boundary (Reacts `` component) to
    display the loading view. Loading boundaries are quite similar to error
    boundaries. They "catch" active loading processes in any child component and
    render a fallback instead. The benefits of this pattern are:

    - No pollution of handling loading states over and over again in your components
    - Definition of loading views, where you need them in the component tree
    - Flexible level of loading views: Sometimes it is less distracting to use a
    single loading view for a larger section of your app, but sometimes small
    pieces of async data should not keep off parts of your app from rendering.
    - Declarative and easy to understand

    Example of how to define loading views:

    ```jsx
    const App = () => (
    }>
    {/* ... */}

    }>


    }>



    {/* ... */}

    );
    ```

    ### Gotchas when defining "built-in" loading views

    When you are using `.use()` or `usePromise()` in your component, it can not
    define its own loading boundary โ€“ at least not for directly used Async
    Resources.

    In this example the fallback component will not be shown:

    ```jsx
    const UserAvatar = ({ userResource, size }) => {
    const user = userResource.use();
    // ๐Ÿ‘† any code below this line will not be executed ๐Ÿ™… until the async loader is done.
    const loadingView = ;
    return (

    {/* This fallback ๐Ÿ‘† is not rendered for the used resource from above. ๐Ÿ˜ข */}


    );
    };
    ```

    The following approaches can help you to solve this issue:

    - Split up your component into two separate components โ€“ one private with the
    regular rendering, and another one wrapping it with a loading boundary while
    forwarding props to it.

    ```jsx
    const Component = ({ userResource, size }) => {
    const user = userResource.use();
    return ;
    };

    export const UserAvatar = (props) => {
    const loadingView = ;

    return (



    );
    };
    ```

    - Opt-out Suspense-based loading behavior.

    ```jsx
    const UserAvatar = ({ userResource, size }) => {
    const user = userResource.use({ useSuspense: false });

    // `user` is an object with `isSet` and `value` properties
    if (!user.isSet) {
    return ;
    }

    // `value` only exists when `isSet === true`
    return ;
    };
    ```

    - Use a Higher Order Component (HOC) that enhances your regular implementation
    with a loading boundary. This HOC could optionally add an error boundary.

    ```jsx
    const UserAvatar = withLoadingBoundary(({ userResource, size }) => {
    const user = userResource.use();
    return ;
    }, AvatarSkeleton);
    ```

    - Use a render component where you access the resource:

    ```jsx
    const UserAvatar = ({ userResource, size }) => {
    const loadingView = ;
    return (


    {() => {
    const user = userResource.use();
    return ;
    }}


    );
    };
    ```

    ## Opt-Out Suspense

    When you want to react explicitly on the loading-state, the suspense-based
    loading behavior is a little bit obstructive, e.g. when you want to define
    components with
    [built-in loading views](#gotchas-when-defining-built-in-loading-views).

    You can opt-out Suspense by setting the `useSuspense` option to `false`. When
    Suspense is disabled, calling `.use()` resp. `usePromise()` will not trigger any
    `` component. Instead, a result object is returned, containing
    loading information and the eventual value.

    Example of how the opt-out behaves:

    ```tsx
    const message = usePromise(loadMessage, ["12345"], { useSuspense: false });

    if (!message.hasValue) {
    return ;
    }

    return (

    );
    ```

    ### Return object with disabled Suspense

    The object returned by `.use()` resp. `usePromise()` has the following
    properties, when Suspense is disabled.

    - `isLoading`: Is `true` when the resource is loading or reloading. `false`
    otherwise.
    - `hasValue`: Is `true` when a resource value is available, including the "old"
    value when reloading. `false` otherwise.
    - `value`: Contains the resource value. Is only available if `hasValue` is
    `true`.
    - `maybeValue`: Contains the resource value, when the resource has loaded.
    `undefined` otherwise.

    Examples of result objects:

    ```js
    // Loading the first time or reloading with keepValueWhileLoading disabled
    ({
    isLoading: true,
    maybeValue: undefined,
    hasValue: false,
    });

    // When value has loaded
    ({
    isLoading: false,
    maybeValue: "Foo",
    value: "Foo",
    hasValue: true,
    });

    // When value is reloading
    ({
    isLoading: true,
    maybeValue: "Foo",
    value: "Foo",
    hasValue: true,
    });
    ```

    ## Opt-Out Loading

    You probably know that you should
    [not call hooks conditionally](https://react.dev/warnings/invalid-hook-call-warning#breaking-rules-of-hooks).
    But what if the loader parameters you want to use e.g. in `usePromise` are
    optional properties, and the loader function requires them? In this case just
    use `null` as parameters and loading is disabled!

    ๐Ÿ˜ˆ Bad:

    ```jsx
    const Username = ({ id }) => {
    if (!id) {
    return null;
    }
    // ๐Ÿ’ฅ This will throw a React error because `usePromise` is called conditionally!
    const user = usePromise(loadUseProfile, [id]);
    return <>{user.name}>;
    };
    ```

    ๐Ÿ˜‡ Good:

    ```jsx
    const Username = ({ id }) => {
    // When required loader parameters can be undefined: use null ๐Ÿ‘‡
    const user = usePromise(loadUseProfile, id ? [id] : null);
    // โš ๏ธ In this case, `user` can also be `undefined`!
    return <>{user ? user.name : "Unknown"}>;
    };
    ```

    ## Error handling

    Errors that occur in any async loader function can be handled with regular error
    boundaries. **With only one exception**. When you have implemented a
    retry-render-mechanism in your error fallback view, the erroneous resource has
    to be refreshed as well before re-rendering.

    When you use the popular
    [`react-error-boundary`](https://www.npmjs.com/package/react-error-boundary)
    package, you can utilize its `onReset` property to refresh all erroneous
    resources, while resetting the error view.

    Example of refreshing erroneous resources on reset:

    ```jsx
    // ...
    import { refresh } from "@mittwald/react-use-promise";
    import { ErrorBoundary } from "react-error-boundary";

    const App = () => (


    }
    onReset={() => {
    refresh({ error: true });
    }}
    >
    }>




    );
    ```

    ## Best practices

    - Use async resources as deep in the component tree as possible (see
    [Lazy loading with Async Resources](#-lazy-loading-with-async-resources)).
    - In larger applications use factory methods for your async resources. The
    [`resourceify`](#resourceify) function helps you to quickly set up your
    factory methods.
    - Add tags to your resources for a nuanced refreshing. You can even listen on
    backend-events (maybe pushed via a socket-connection) to invalidate the
    resources used in your session.
    - Use models for a coherent interaction with your business models. This is a
    good practice in general, when your app has to deal with a large business
    model.

    ```tsx
    // File resources/user.ts
    import { getUserProfile } from "../resources/user";
    import { resourceify } from "@mittwald/react-use-promise";

    export const getUserProfile = resourceify(apiClient.loadUserProfile);

    // File models/UserProfile.ts
    import { getUserProfile } from "../resources/user";
    // ...

    export class UserProfile {
    public readonly id: string;
    public readonly firstName: string;
    public readonly lastName: string;

    public constructor(data: UserProfileApiData) {
    this.id = data.id;
    this.firstName = data.firstName;
    this.lastName = data.lastName;
    }

    public static useLoadById(id: string): UserProfile {
    const data = getUserProfile([id]).use();
    return new UserProfile(data);
    }

    public getFullName(): string {
    return `${this.firstName} ${this.lastName}`;
    }

    public async updateName(firstName: string, lastName: string): Promise {
    await apiClient.updateProfile({
    firstName,
    lastName,
    });
    }
    }

    // File components/UserProfileName.tsx
    import { UserProfile } from "../models/UserProfile";

    export const UserProfileName: FC<{ id: string }> = (props) => {
    const user = UserProfile.useLoadById(props.id);
    return <>{user.getFullName()}>;
    };
    ```

    ## Migration guides

    ### V1 to V2

    - For more naming consistency the `.watch()` method of the Async Resource has
    changed to `.use()`. Replace all usages of `watch()` with `use()`.