RD

reactive/data-client

Developer tools
2K stars Quality 71 Trend 76Apache-2.0

Async State Management without the Management. REST, GraphQL, SSE, Websockets

Overview

The scalable way to build applications with dynamic data.  |  🏁Getting Started  |  🤖Agent Skills 🎮 Demos:   Todo  |  Github Social  |  NextJS SSR  |  Websockets+SSR For more details, see the Getting Started docs page. For the small price of 9kb gziped.    🏁Get started now - [x] 🛌 React Suspense support - [x] 🧵 React 18 Concurrent mode compatible - [x] 💦 Partial Hydration Server Side Rendering - [x] 🎣 Declarative API - [x] 📝 Composition over configuration - [x] 💰 Normalized caching - [x] 💥 Tiny bundle footprint - [x] 🛑 Automatic overfetching elimination - [x] ✨ Fast optimistic updates - [x] 🧘 Flexible to fit any API design (one size fits all) - [x] 🔧 Debugging and inspection via browser extension - [x] 🌳 Tree-shakable (only use what you need) - [x] 🔁 Subscriptions - [x] ♻️ Optional redux integration - [x] 📙 Storybook mocking - [x] 📱 React Native support - [x] 📱 Expo support - [x] ⚛️ NextJS support -...

README

Installation

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

For more details, see the Getting Started docs page.

Skills

npx skills add reactive/data-client

Then run skill “data-client-setup”

Skills also ship inside each @data-client/* package; load them with TanStack Intent (npx @tanstack/intent@latest install).

Usage

Simple TypeScript definition

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

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

  static schema = {
    author: User,
    createdAt: Temporal.Instant.from,
  };
}

Create collection of API Endpoints

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.title} by {article.author.username}
    
    {article.body}
  
);

Reactive Mutations

const ctrl = useController();
return (
  <>
    <CreateArticleForm
      onSubmit={article =>
        ctrl.fetch(ArticleResource.getList.push, article)
      }
    />
    <ProfileForm
      onSubmit={user =>
        ctrl.fetch(UserResource.update, { id: article.author.id }, user)
      }
    />
     ctrl.fetch(ArticleResource.delete, { id })}>
      Delete
    
  
);

Subscriptions

const price = useLive(PriceResource.get, { symbol });
return price.value;

Type-safe Imperative Actions

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

Programmatic queries

const queryTotalVotes = new Query(
  new Collection([BlogPost]),
  posts => posts.reduce((total, post) => total + post.votes, 0),
);

const totalVotes = useQuery(queryTotalVotes);
const totalVotesForUser = useQuery(queryTotalVotes, { userId });
const groupTodoByUser = new Query(
  TodoResource.getList.schema,
  todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);

Powerful Middlewares

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

Integrated data mocking

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

…all typed …fast …and consistent

For the small price of 9kb gzipped.    🏁Get started now

Features

Examples

  • Todo: | |
  • Github: |
  • NextJS: | |
  • Websockets: | |

API

Reactive Applications

Define Data

Networking definition

View this README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add reactive/data-client