Skip to main content

v7 - React Native, Web and SSR upgrades and more

· 5 min read
Nathaniel Tucker

Rest Hooks 7

For most people, upgrading to Rest Hooks 7 is as easy as upgrading the packages as long as you aren’t using previously (2 years ago) deprecated exports.

npm install --save rest-hooks@7 @rest-hooks/react@6 @rest-hooks/redux@6 @rest-hooks/test@9 @rest-hooks/[email protected]

The big difference here is all react-specific code has been moved into @rest-hooks/react, which is now a peer dependency of the other packages. The rest-hooks package re-exports everything from @rest-hooks/react.

Upgrade to Rest Hooks 7 guide

@rest-hooks/react@7

Once the rest-hooks package is upgraded, you can optionally upgrade @rest-hooks/react to 7.

npm install --save @rest-hooks/react@7

React Native

Because the React Native and Web interfaces are the same, we ship them in the same package and delivery appropriate specializations where appropriate.

The only breaking change is that useSuspense, useSubscription, useLive, useFetch are all react-native aware. This is unlikely to cause any issue, as screen focus will trigger fetches on stale data.

@rest-hooks/react@7.1

New additions in 7.1

@rest-hooks/ssr@0.7

Newly added guide and utilities specific for making NextJS integration easier.

npm install --save @rest-hooks/ssr @rest-hooks/redux redux
pages/_document.tsx
import { RestHooksDocument } from '@rest-hooks/ssr/nextjs';

export default RestHooksDocument;
pages/_app.tsx
import { AppCacheProvider } from '@rest-hooks/ssr/nextjs';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
return (
<AppCacheProvider>
<Component {...pageProps} />
</AppCacheProvider>
);
}

Demo

See full SSR guide for NextJS

LogoutManager

Secure authentication expires at some point. Typically this results in APIs responding with a 401 status. To provide a batteries-included solution for this case, LogoutManager was introduced. It's fully configurable so be sure to check out the docs for more details.

/index.tsx
import { CacheProvider, LogoutManager } from '@rest-hooks/react';
import ReactDOM from 'react-dom';

const managers = [new LogoutManager(), ...CacheProvider.defaultProps.managers];

ReactDOM.createRoot(document.body).render(
<CacheProvider managers={managers}>
<App />
</CacheProvider>,
);

PR #2293

useLive

Often useSubscription() is used in the same components that useSuspense() is. To reduce verbosity we have introduced useLive() which simply calls both useSubscription() and useSuspense().

api/ExchangeRates.ts
import { Entity, RestEndpoint } from '@rest-hooks/rest';
export class ExchangeRates extends Entity {
readonly currency: string = 'USD';
readonly rates: Record<string, string> = {};
pk(): string {
return this.currency;
}
}
export const getExchangeRates = new RestEndpoint({
urlPrefix: 'https://www.coinbase.com/api/v2',
path: '/exchange-rates',
searchParams: {} as { currency: string },
schema: { data: ExchangeRates },
pollFrequency: 15000,
});
ProfileList.tsx
import { useLive } from '@rest-hooks/react';
import { getExchangeRates } from './api/ExchangeRates';
function AssetPrice({ symbol }: Props) {
const { data: price } = useLive(getExchangeRates, {
currency: 'USD',
});
const displayPrice = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(1 / Number.parseFloat(price.rates[symbol]));
return (
<span>
{symbol} {displayPrice}
</span>
);
}
interface Props {
symbol: string;
}
render(<AssetPrice symbol="BTC" />);
Live Preview
Loading...
Store

PR #2287

AsyncBoundary

Suspense and NetworkErrorBoundary are often co-located. To simplify this common case we introduced AsyncBoundary

import { AsyncBoundary } from '@rest-hooks/react';

function App() {
return (
<AsyncBoundary>
<AnotherRoute />
<TodoDetail id={5} />
</AsyncBoundary>
);
}

PR #2270

Manager.getMiddleware() API changes

Manager middleware has been designed to be compatible with redux. This means the original API had { dispatch, getState } as its arguments to the middleware.

Controller is a superset of this functionality, and its methods provide a more type-safe way of interacting with the flux lifecycle. Because of this we have moved to pass the whole controller, instead of just dispatch, and getState.

class Controller {
/*************** Action Dispatchers ***************/
fetch(endpoint, ...args) => ReturnType<E>;
invalidate(endpoint, ...args) => Promise<void>;
resetEntireStore: () => Promise<void>;
receive(endpoint, ...args, response) => Promise<void>;
receiveError(endpoint, ...args, error) => Promise<void>;
resolve(endpoint, { args, response, fetchedAt, error }) => Promise<void>;
subscribe(endpoint, ...args) => Promise<void>;
unsubscribe(endpoint, ...args) => Promise<void>;
/*************** Data Access ***************/
getResponse(endpoint, ...args, state)=> { data, expiryStatus, expiresAt };
getError(endpoint, ...args, state)=> ErrorTypes | undefined;
snapshot(state: State<unknown>, fetchedAt?: number): SnapshotInterface;
getState(): State<unknown>;
}

Of course existing Managers just using dispatch and/or getState will continue to work.

PR #2290