Rest Hooks

Async State Management without the Management. REST, GraphQL, SSE, Websockets
GitHub
1.94k
Created 6 years ago, last commit 8 hours ago
39 contributors
4.34k commits
Stars added on GitHub, month by month
0
11
12
1
2
3
4
5
6
7
8
9
10
2023
2024
Stars added on GitHub, per day, on average
Yesterday
=
Last week
+0.3
/day
Last month
+0.3
/day
Last 12 months
+0.2
/day
npmPackage on NPM
Monthly downloads on NPM
11
12
1
2
3
4
5
6
7
8
9
10
2023
2024
README
Reactive Data Client

The scalable way to build applications with dynamic data.

Declarative resouce definitons for REST, GraphQL, Websockets+SSE and more
Performant rendering in React, NextJS, React Native, ExpoGo

Schema driven. Zero updater functions.

CircleCI Coverage Status Percentage of issues still open bundle size npm version PRs Welcome Chat

๐Ÿ“–Read The Docs ย |ย  ๐ŸGetting Started
๐ŸŽฎ Demos: ย  Todo ย |ย  Github Social ย |ย  NextJS SSR ย |ย  Websockets+SSR

Installation

npm install --save @data-client/react @data-client/rest @data-client/test

For more details, see the Installation docs page.

Usage

class User extends Entity {
  id = '';
  username = '';
}

class Article extends Entity {
  id = '';
  title = '';
  body = '';
  author = User.fromJS();
  createdAt = Temporal.Instant.fromEpochSeconds(0);

  static schema = {
    author: User,
    createdAt: Temporal.Instant.from,
  };
}
const UserResource = resource({
  path: '/users/:id',
  schema: User,
  optimistic: true,
});

const ArticleResource = resource({
  path: '/articles/:id',
  schema: Article,
  searchParams: {} as { author?: string },
  optimistic: true,
  paginationField: 'cursor',
});

One line data binding

const article = useSuspense(ArticleResource.get, { id });
return (
  <article>
    <h2>
      {article.title} by {article.author.username}
    </h2>
    <p>{article.body}</p>
  </article>
);
const ctrl = useController();
return (
  <>
    <CreateArticleForm
      onSubmit={article =>
        ctrl.fetch(ArticleResource.getList.push, { id }, article)
      }
    />
    <ProfileForm
      onSubmit={user =>
        ctrl.fetch(UserResource.update, { id: article.author.id }, user)
      }
    />
    <button onClick={() => ctrl.fetch(ArticleResource.delete, { id })}>
      Delete
    </button>
  </>
);
const price = useLive(PriceResource.get, { symbol });
return price.value;
const ctrl = useController();
await ctrl.fetch(ArticleResource.update, { id }, articleData);
await ctrl.fetchIfStale(ArticleResource.get, { id });
ctrl.expireAll(ArticleResource.getList);
ctrl.invalidate(ArticleResource.get, { id });
ctrl.invalidateAll(ArticleResource.getList);
ctrl.setResponse(ArticleResource.get, { id }, articleData);
ctrl.set(Article, { id }, articleData);
const queryTotalVotes = new schema.Query(
  new schema.Collection([BlogPost]),
  posts => posts.reduce((total, post) => total + post.votes, 0),
);

const totalVotes = useQuery(queryTotalVotes);
const totalVotesForUser = useQuery(queryTotalVotes, { userId });
const groupTodoByUser = new schema.Query(
  TodoResource.getList.schema,
  todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);
class LoggingManager implements Manager {
  middleware: Middleware = controller => next => async action => {
    console.log('before', action, controller.getState());
    await next(action);
    console.log('after', action, controller.getState());
  };

  cleanup() {}
}
class TickerStream implements Manager {
  middleware: Middleware = controller => {
    this.handleMsg = msg => {
      controller.set(Ticker, { id: msg.id }, msg);
    };
    return next => action => next(action);
  };

  init() {
    this.websocket = new WebSocket('wss://ws-feed.myexchange.com');
    this.websocket.onmessage = event => {
      const msg = JSON.parse(event.data);
      this.handleMsg(msg);
    };
  }
  cleanup() {
    this.websocket.close();
  }
}
const fixtures = [
  {
    endpoint: ArticleResource.getList,
    args: [{ maxResults: 10 }] as const,
    response: [
      {
        id: '5',
        title: 'first post',
        body: 'have a merry christmas',
        author: { id: '10', username: 'bob' },
        createdAt: new Date(0).toISOString(),
      },
      {
        id: '532',
        title: 'second post',
        body: 'never again',
        author: { id: '10', username: 'bob' },
        createdAt: new Date(0).toISOString(),
      },
    ],
  },
  {
    endpoint: ArticleResource.update,
    response: ({ id }, body) => ({
      ...body,
      id,
    }),
  },
];

const Story = () => (
  <MockResolver fixtures={options[result]}>
    <ArticleList maxResults={10} />
  </MockResolver>
);

...all typed ...fast ...and consistent

For the small price of 9kb gziped. ย ย  ๐ŸGet started now

Features

Examples

  • Todo: GitHub | Sandbox | Edit on CodeSandbox
  • Github: GitHub | Sandbox
  • NextJS: GitHub | Sandbox | Edit on CodeSandbox
  • Websockets: GitHub | Sandbox | Website

API

Reactive Applications

Networking definition

Data model
Data Type Mutable Schema Description Queryable
Object โœ… Entity, EntityMixin single unique object โœ…
โœ… Union(Entity) polymorphic objects (A | B) โœ…
๐Ÿ›‘ Object statically known keys ๐Ÿ›‘
Invalidate(Entity) delete an entity ๐Ÿ›‘
List โœ… Collection(Array) growable lists โœ…
๐Ÿ›‘ Array immutable lists ๐Ÿ›‘
โœ… All list of all entities of a kind โœ…
Map โœ… Collection(Values) growable maps โœ…
๐Ÿ›‘ Values immutable maps ๐Ÿ›‘
any Query(Queryable) memoized custom transforms โœ