UNPKG

@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
# `@shopify/react-network` [![Build Status](https://github.com/Shopify/quilt/workflows/Node-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ANode-CI) [![Build Status](https://github.com/Shopify/quilt/workflows/Ruby-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ARuby-CI) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) [![npm version](https://badge.fury.io/js/%40shopify%2Freact-network.svg)](https://badge.fury.io/js/%40shopify%2Freact-network.svg) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@shopify/react-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. ## Installation ```bash yarn add @shopify/react-network ``` ## Usage 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. ### Application 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. #### `useRedirect()` and `<Redirect />` 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} />; } ``` #### `useStatus()` and `<Status />` 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} />; } ``` #### `useCspDirective()` and content security policy components 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 /> </> ); } ``` #### `useHeader()` and `useRequestHeader()` 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>; } ``` #### `useAcceptLanguage()` 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>; } ``` #### `useNetworkManager()` 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> ); } ``` #### `<NetworkUniversalProvider />` 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`. ### Server 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. ### Other utilities 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.