Skip to main content

Snapshot

Snapshots passed to user-defined function that are used to compute state updates. These allow safe and performant access to the denormalized data based on the current state.

interface Snapshot {
get(schema, ...args)=> DenormalizeNullable<typeof schema> | undefined;
getResponse(endpoint, ...args)=> { data, expiryStatus, expiresAt };
getError(endpoint, ...args)=> ErrorTypes | undefined;
fetchedAt: number;
abort: Error;
}
tip

Use Controller.snapshot() to construct a snapshot

Usage

import { Entity } 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 `//loremflickr.com/96/72/kitten,cat?lock=${this.id % 16}`;
  }
}
import { createResource } from '@data-client/rest';
import { Post } from './Post';

export { Post };

export const PostResource = createResource({
  path: '/posts/:id',
  schema: Post,
}).extend('vote', {
  path: '/posts/:id/vote',
  method: 'POST',
  body: undefined,
  schema: Post,
  getOptimisticResponse(snapshot, { id }) {
    const post = snapshot.get(Post, { id });
    if (!post) throw snapshot.abort;
    return {
      id,
      votes: post.votes + 1,
    };
  },
});
import { useController } from '@data-client/react';
import { PostResource, type Post } from './PostResource';

export default function PostItem({ post }: Props) {
  const ctrl = useController();
  const handleVote = () => {
    ctrl.fetch(PostResource.vote, { id: post.id });
  };
  return (
    <div>
      <div className="voteBlock">
        <small className="vote">
          <button className="up" onClick={handleVote}>
            &nbsp;
          </button>
          {post.votes}
        </small>
        <img src={post.img} width="70" height="52" />
      </div>
      <div>
        <h4>{post.title}</h4>
        <p>{post.body}</p>
      </div>
    </div>
  );
}
interface Props { post: Post }
import { schema } from '@data-client/rest';
import { useQuery } from '@data-client/react';
import { Post } from './PostResource';

const queryTotalVotes = new schema.Query(
  new schema.All(Post),
  (posts, { userId } = {}) => {
    if (userId !== undefined)
      posts = posts.filter(post => post.userId === userId);
    return posts.reduce((total, post) => total + post.votes, 0);
  },
);

export default function TotalVotes({ userId }: Props) {
  const totalVotes = useQuery(queryTotalVotes, { userId });
  return (
    <center>
      <small>{totalVotes} votes total</small>
    </center>
  );
}
interface Props { userId: number }
import { useSuspense } from '@data-client/react';
import { PostResource } from './PostResource';
import PostItem from './PostItem';
import TotalVotes from './TotalVotes';

function PostList() {
  const userId = 2;
  const posts = useSuspense(PostResource.getList, { userId });
  return (
    <div>
      {posts.map(post => (
        <PostItem key={post.pk()} post={post} />
      ))}
      <TotalVotes userId={userId} />
    </div>
  );
}
render(<PostList />);
🔴 Live Preview
Store

Members

get(schema, ...args)

Looks up any Queryable Schema.

getResponse(endpoint, ...args)

returns
{
data: DenormalizeNullable<E['schema']>;
expiryStatus: ExpiryStatus;
expiresAt: number;
}

Gets the (globally referentially stable) response for a given endpoint/args pair from state given.

data

The denormalize response data. Guarantees global referential stability for all members.

expiryStatus

export enum ExpiryStatus {
Invalid = 1,
InvalidIfStale,
Valid,
}
Valid
  • Will never suspend.
  • Might fetch if data is stale
InvalidIfStale
  • Will suspend if data is stale.
  • Might fetch if data is stale
Invalid
  • Will always suspend
  • Will always fetch

expiresAt

A number representing time when it expires. Compare to Date.now().

getError(endpoint, ...args)

Gets the error, if any, for a given endpoint. Returns undefined for no errors.

fetchedAt

When the fetch was called that resulted in this snapshot.

abort

This is an Error to be thrown in Endpoint.getOptimisticResponse() to cancel an optimistic update.