v0.9: DevTools, Resource.extend, and Legacy Cleanup
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',
});
Breaking Changes:
- Remove all /next exports
- makeCacheProvider removed
- DELETE -> INVALIDATE action type
- Legacy schema support dropped
- Schema Serializers must support function calls
- Action types prefixed with 'rdc'
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
classNameto ErrorBoundary anderrorClassNameto AsyncBoundary #2785 - New
getDefaultManagers()export for explicit manager control #2791 - Replace BackupBoundary with UniversalSuspense + BackupLoading #2803
- Entity.process() receives endpoint
argsas fourth parameter (since 0.7) a8936f5 nonFilterArgumentKeysfor Collection to exclude sort/order params from filtering (since 0.7) 318df89
Migration Guide
This upgrade requires updating all package versions simultaneously.
- NPM
- Yarn
- pnpm
- esm.sh
yarn add @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
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
pnpm add @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
<script type="module">
import * from 'https://esm.sh/@data-client/react@^0.9.0';
import * from 'https://esm.sh/@data-client/rest@^0.9.0';
import * from 'https://esm.sh/@data-client/test@^0.9.0';
import * from 'https://esm.sh/@data-client/img@^0.9.0';
import * from 'https://esm.sh/@data-client/hooks@^0.9.0';
</script>
Remove /next exports
All /next subpath exports have been removed. Features previously in /next are now
the standard exports. f65cf83
import { useController } from '@data-client/react/next';
import { useController } from '@data-client/react';
makeCacheProvider removed
Use the provider component directly with makeRenderDataClient. #2787
import { makeCacheProvider } from '@data-client/react';
const renderDataClient = makeRenderDataClient(makeCacheProvider);
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
import { DELETE_TYPE } from '@data-client/react';
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.expiresAtremoved- 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
class MyEntity extends Entity {
createdAt = new Date();
static schema = {
createdAt: Date,
};
}
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
import { ReceiveAction, RECEIVE_TYPE } from '@data-client/react';
controller.receive(endpoint, args, response);
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
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() {}
}
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 or file a bug
