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);
      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
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