Skip to main content

v0.9: DevTools, Resource.extend, and Legacy Cleanup

· 7 min read
Nathaniel Tucker
Creator of Reactive Data Client

This release focuses on developer experience with a devtools button that appears in development mode, better DevTools history, and new controller methods like controller.expireAll() and controller.fetchIfStale().

Resource.extend() provides three powerful ways to customize resources - adding new endpoints, overriding existing ones, or deriving from base endpoints.

const UserResource = createResource({
path: '/users/:id',
schema: User,
}).extend('current', {
path: '/users/current',
});
Migration guide

Breaking Changes:

Resource.extend() (since 0.5)

Resource.extend() provides three powerful ways to customize resources. 51b4b0d

Add new endpoints

const UserResource = createResource({
path: '/users/:id',
schema: User,
}).extend('current', {
path: '/users/current',
});

Override existing endpoints

const CachedArticleResource = ArticleResource.extend({
getList: {
dataExpiryLength: 10 * 60 * 1000, // 10 minutes
},
});

Derive from base endpoints

const IssueResource = createResource({
path: '/issues/:id',
schema: Issue,
}).extend(Base => ({
byRepo: Base.getList.extend({
path: '/repos/:owner/:repo/issues',
}),
}));

Controller Methods

controller.expireAll() (since 0.3)

Sets all matching responses to stale, triggering background refetch while showing existing data. #2802

// Mark all article lists as stale - they'll refetch when rendered
controller.expireAll(ArticleResource.getList);

This differs from invalidateAll() which removes the data entirely. expireAll() keeps showing the cached data while fetching fresh data in the background.

controller.fetchIfStale() (since 0.4)

Fetches only if data is considered stale; otherwise returns the cached data. #2804

// Perfect for prefetching - won't overfetch fresh data
const resolveData = async (controller, { owner, repo }) => {
await controller.fetchIfStale(IssueResource.getList, { owner, repo });
};

DevTools Improvements

DevTools Button

A floating button now appears in development mode to quickly open the Redux DevTools extension. #2803

Configure or disable it via the devButton prop:

// Disable the button
<CacheProvider devButton={null}>
<App />
</CacheProvider>
// Position it differently
<CacheProvider devButton="top-right">
<App />
</CacheProvider>

Persistent History

DevTools no longer forgets history if not open on page load. 2d2e941

Better State Tracking

Since React 18 batches updates, the real state can sometimes update from multiple actions. When devtools are open, a shadow state accurately reflects changes from each action for easier debugging. c9ca31f

Endpoint Properties Visible

Endpoint properties are now fully visible in the devtool inspector. a7da00e

Collection Enhancements

FormData argument filtering (since 0.4)

Collections can now filter based on FormData arguments. f95dbc6

ctrl.fetch(getPosts.push, { group: 'react' }, new FormData(e.currentTarget));

If the FormData contains an author field, the newly created item will be properly added to collections filtered by that author.

paginationField (since 0.7)

Use paginationField for easy pagination support. c8c557

const TodoResource = createResource({
path: '/todos/:id',
schema: Todo,
paginationField: 'page',
});

// Fetches page 2 and appends to the collection
ctrl.fetch(TodoResource.getList.getPage, { page: '2' });

Other Improvements

  • Add className to ErrorBoundary and errorClassName to AsyncBoundary #2785
  • New getDefaultManagers() export for explicit manager control #2791
  • Replace BackupBoundary with UniversalSuspense + BackupLoading #2803
  • Entity.process() receives endpoint args as fourth parameter (since 0.7) a8936f5
  • nonFilterArgumentKeys for Collection to exclude sort/order params from filtering (since 0.7) 318df89

Migration Guide

This upgrade requires updating all package versions simultaneously.

npm install --save @data-client/react@^0.9.0 @data-client/rest@^0.9.0 @data-client/test@^0.9.0 @data-client/img@^0.9.0 @data-client/hooks@^0.9.0

Remove /next exports

All /next subpath exports have been removed. Features previously in /next are now the standard exports. f65cf83

Before
import { useController } from '@data-client/react/next';
After
import { useController } from '@data-client/react';

makeCacheProvider removed

Use the provider component directly with makeRenderDataClient. #2787

Before
import { makeCacheProvider } from '@data-client/react';
const renderDataClient = makeRenderDataClient(makeCacheProvider);
After
import { CacheProvider } from '@data-client/react';
const renderDataClient = makeRenderDataClient(CacheProvider);

DELETE -> INVALIDATE action type

The DELETE action type has been renamed to INVALIDATE for clarity. #2784

Before
import { DELETE_TYPE } from '@data-client/react';
After
import { INVALIDATE_TYPE } from '@data-client/react';

Legacy schema support dropped

All support for legacy schemas has been removed. Ensure you're using the current Entity class. #2784

  • entity.expiresAt removed
  • All Entity overrides for backwards compatibility removed

Schema Serializers must support function calls

Schema Serializers must now support function calls. Date no longer works directly. #2795

Before
class MyEntity extends Entity {
createdAt = new Date();

static schema = {
createdAt: Date,
};
}
After
class MyEntity extends Entity {
createdAt = new Date();

static schema = {
createdAt: (iso: string) => new Date(iso),
};
}

Alternatively, use Temporal:

import { Temporal } from '@js-temporal/polyfill';

class MyEntity extends Entity {
createdAt = Temporal.Instant.fromEpochSeconds(0);

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

Action types prefixed with 'rdc'

All action types are now prefixed with 'rdc' for better namespacing. #2781

receive -> set action names

All 'receive' action names have been renamed to 'set'. #2782

Before
import { ReceiveAction, RECEIVE_TYPE } from '@data-client/react';
controller.receive(endpoint, args, response);
After
import { SetAction, SET_TYPE } from '@data-client/react';
controller.set(endpoint, args, response);

Middleware API simplified

Middleware no longer receives controller as a destructured property - it receives controller directly. #2786

Before
class LoggingManager implements Manager {
getMiddleware = (): Middleware => ({ controller }) => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
};

cleanup() {}
}
After
class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
};

cleanup() {}
}

getDefaultManagers()

CacheProvider elements no longer share default managers. Use getDefaultManagers() for explicit control. #2791

import { getDefaultManagers, CacheProvider } from '@data-client/react';

const managers = getDefaultManagers();

<CacheProvider managers={managers}>
<App />
</CacheProvider>

Upgrade support

As usual, if you have any troubles or questions, feel free to join our Chat or file a bug