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 '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