Skip to main content

useLoading()

Helps track loading state of imperative async functions.

tip

useSuspense() or useDLE() are better for GET/read endpoints.

Usage

npm install --save @data-client/hooks
import { Entity, createResource } from '@data-client/rest';

export class Post extends Entity {
  id = 0;
  userId = 0;
  title = '';
  body = '';
  votes = 0;

  pk() {
    return this.id?.toString();
  }
  static key = 'Post';

  get img() {
    return `//placekitten.com/96/72?image=${this.id % 16}`;
  }
}
export const PostResource = createResource({
  path: '/posts/:id',
  schema: Post,
});
import { useSuspense } from '@data-client/react';
import { PostResource } from './PostResource';

export default function PostDetail({ id }) {
  const post = useSuspense(PostResource.get, { id });
  return (
    <div>
      <div className="voteBlock">
        <img src={post.img} width="70" height="52" />
      </div>
      <div>
        <h4>{post.title}</h4>
        <p>{post.body}</p>
      </div>
    </div>
  );
}
export default function PostForm({ onSubmit, loading, error }) {
  const handleSubmit = e => {
    e.preventDefault();
    const data = new FormData(e.target);
    onSubmit(data);
  };
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Title:
        <br />
        <input type="text" name="title" defaultValue="My New Post" required />
      </label>
      <br />
      <label>
        Body:
        <br />
        <textarea name="body" rows={12} required>
          After clicking 'save', the button will be disabled until the POST is
          completed. Upon completion the newly created post is displayed
          immediately as Reactive Data Client is able to use the fetch response
          to populate the store.
        </textarea>
      </label>
      {error ? (
        <div className="alert alert--danger">{error.message}</div>
      ) : null}
      <div>
        <button type="submit" disabled={loading}>
          {loading ? 'saving...' : 'Save'}
        </button>
      </div>
    </form>
  );
}
import { useController } from '@data-client/react';
import { useLoading } from '@data-client/hooks';
import { PostResource } from './PostResource';
import PostForm from './PostForm';

export default function PostCreate({ navigateToPost }) {
  const ctrl = useController();
  const [handleSubmit, loading, error] = useLoading(
    async data => {
      const post = await ctrl.fetch(PostResource.getList.push, data);
      // React 17 does not batch updates
      // so we wait for the new post to be commited to the React
      // store to avoid additional fetches
      requestIdleCallback(() => navigateToPost(post.id));
    },
    [ctrl],
  );
  return <PostForm onSubmit={handleSubmit} loading={loading} error={error} />;
}
import PostCreate from './PostCreate';
import PostDetail from './PostDetail';

function Navigation() {
  const [id, setId] = React.useState<undefined | number>(undefined);
  if (id) {
    return (
      <div>
        <PostDetail id={id} />
        <center>
          <button onClick={() => setId(undefined)}>New Post</button>
        </center>
      </div>
    );
  }
  return <PostCreate navigateToPost={setId} />;
}
render(<Navigation />);
Live Preview
Loading...
Store

Eslint

Eslint configuration

Since we use the deps list, be sure to add useLoading to the 'additionalHooks' configuration of react-hooks/exhaustive-deps rule if you use it.

{
"rules": {
// ...
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "(useLoading)"
}]
}
}

Types

export default function useLoading<F extends (...args: any) => Promise<any>>(
func: F,
deps: readonly any[] = [],
): [F, boolean];

Part of @data-client/hooks