@shopify/react-network
Version:
A collection of components that allow you to set common HTTP headers from within your React application
230 lines (168 loc) • 8.86 kB
Markdown
[](https://github.com/Shopify/quilt/actions?query=workflow%3ANode-CI)
[](https://github.com/Shopify/quilt/actions?query=workflow%3ARuby-CI)
[](LICENSE.md) [](https://badge.fury.io/js/%40shopify%2Freact-network.svg) [](https://img.shields.io/bundlephobia/minzip/@shopify/react-network.svg)
A collection of components that allow you to set common HTTP headers from within your React application.
```bash
yarn add @shopify/react-network
```
This package uses [`@shopify/react-effect`](https://github.com/Shopify/quilt/tree/main/packages/react-effect) to allow your application to communicate various HTTP-related details to the Node server doing React rendering. It also provides a utility function for easily applying these details to a Koa context object.
This library provides a number of React hooks and components you can use anywhere in your application to register network-related details on the server.
Specifies a redirect location. `applyToContext` will call `ctx.redirect()` with the passed URL, and set the status code, if you pass the `code` prop.
```tsx
import {useRedirect, Redirect, StatusCode} from '@shopify/react-network';
function MyComponent() {
useRedirect('/login', StatusCode.SeeOther);
// or
return <Redirect url="/login" code={StatusCode.SeeOther} />;
}
```
Specifies a status code. `applyToContext` will set `ctx.status` with the passed status code. If multiple status codes are set during the navigation of the tree, the most "significant" one will be used — that is, the status code that is the highest numerically.
```tsx
import {useStatus, Status, StatusCode} from '@shopify/react-network';
function MyComponent() {
useStatus(StatusCode.NotFound);
// or
return <Status code={StatusCode.SeeOther} />;
}
```
This package exports a `useCspDirective()` hook (and many components) for constructing a [content security policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). Every CSP directive has a matching component in this library that exposes a nice API for setting that directive. When `applyToContext` is run, it will group together all of the directives and set the CSP header.
There are too many to go over individually, but the example below illustrates setting up a simple CSP. Review the available imports from the library for all available components.
```tsx
import {
useCspDirective,
DefaultSource,
StyleSource,
SpecialSource,
CspDirective,
UpgradeInsecureRequests,
} from '@shopify/react-network';
export default function ContentSecurityPolicy() {
useCspDirective(CspDirective.DefaultSrc, [SpecialSource.Self]);
useCspDirective(CspDirective.StyleSrc, [
SpecialSource.Self,
SpecialSource.UnsafeInline,
]);
useCspDirective(CspDirective.UpgradeInsecureRequests, true);
// OR
return (
<>
<DefaultSource sources={[SpecialSource.Self]} />
<StyleSource sources={[SpecialSource.Self, SpecialSource.UnsafeInline]} />
<UpgradeInsecureRequests />
</>
);
}
```
This library allows you to read from request headers, and set response headers. To set a header, call the `useHeader()` hook, which accepts the name of a header and the desired value. `useRequestHeader()`, on the other hand, gives you access to a specified request header.
**Note:** calling `useRequestHeader` on client-side renders will give you `undefined`, since we only have access to the request context on the server. To remedy this, wrap your app in a `NetworkUniversalProvider` (see below for more details).
```tsx
import {useHeader, useRequestHeader} from '@shopify/react-network';
function MyComponent() {
useHeader('X-React', 'true');
const acceptLanguage = useRequestHeader('Accept-Language');
return <div>Requested languages: {acceptLanguage}</div>;
}
```
This hook will read and parse the value of the `Accept-Language` header and return the result in an array of `Language` objects. It takes one argument as the fallback `Language` in case the header is not present.
**Note:** `useAcceptLanguage` calls `useRequestHeader`, so the constraints on client-side renders apply here too. Wrap your app in a `NetworkUniversalProvider` and pass in `[Header.AcceptLanguage]` to the `headers` prop in order to call `useAcceptLanguage` on subsequent client-side renders.
```tsx
import {useAcceptLanguage} from '@shopify/react-network';
function MyComponent() {
const fallback = {code: 'en', quality: 1.0};
const locales = useAcceptLanguage(fallback);
const languages = locales.map(({code, quality, region}) => {
return `code: ${code}, quality: ${quality}, region: ${region}`;
});
return <div>Requested languages: {languages}</div>;
}
```
Returns the full network manager from context.
```tsx
import React from 'react';
import {useNetworkManager} from '@shopify/react-network';
import {CookieContext} from './context';
export function CookieProvider({children}: Props) {
const manager = useNetworkManager();
return (
<CookieContext.Provider value={manager.cookies}>
{children}
</CookieContext.Provider>
);
}
```
In the case you need to have access to network details on both client and server-side renders, you can wrap your top-level app in `NetworkUniversalProvider` like so:
```tsx
export default function App() {
return (
<NetworkUniversalProvider
headers={['x-some-header', 'x-some-other-header']}
>
{
// rest of your app
}
</NetworkUniversalProvider>
);
}
```
Note that `NetworkContext.Provider` has to be rendered somewhere above in your app (see below).
Currently this universal provider only supports headers, so you can pass in an array of header names to the `headers` prop. Then, in components nested further down in your tree you can get those headers from context using `useRequestHeader` on client-side renders like so:
```tsx
export default function SomeInnerComponent() {
const someHeaderValue = useRequestHeader('x-some-header');
const someOtherHeaderValue = useRequestHeader('x-some-other-header');
return (
<Markup
value={someHeaderDependentLogic(someHeaderValue, someOtherHeaderValue)}
/>
);
}
```
`headers` aren't case-sensitive, but it's a good idea to keep consistent between `NetworkUniversalProvider` and `useRequestHeader`.
To extract details from your application, render a `NetworkContext.Provider` around your app, and give it an instance of `NetworkManager`. When using `react-effect`, this decoration can be done in the `decorate` option of `extract()`. Finally, you can use the `applyToContext` utility from this package to apply the necessary headers to the response. Your final server middleware will resemble the example below:
```tsx
import React from 'react';
import {render} from '@shopify/react-html/server';
import {extract} from '@shopify/react-effect/server';
import {
NetworkManager,
NetworkContext,
applyToContext,
} from '@shopify/react-network/server';
import App from './App';
export default function renderApp(ctx: Context) {
// Accepts an optional headers argument for giving access
// to request headers.
const networkManager = new NetworkManager({
headers: ctx.headers,
});
const app = <App />;
await extract(app, {
decorate: (element) => (
<NetworkContext.Provider value={networkManager}>
{element}
</NetworkContext.Provider>
),
});
applyToContext(ctx, networkManager);
ctx.body = render(
<NetworkContext.Provider value={networkManager}>
{app}
</NetworkContext.Provider>,
);
}
```
> Note: You can selectively extract _only_ the network details by using the `EFFECT_ID` exported from `@shopify/react-network/server`, and using this as the second argument to `@shopify/react-effect`’s `extract()` as detailed in its documentation. Most consumers of this package will be fine with just the example above.
This library re-exports the entirety of [`@shopify/network`](https://github.com/Shopify/quilt/tree/main/packages/network), so you do not need to install both.