Skip to content

Commit

Permalink
Support status codes for redirects (#717)
Browse files Browse the repository at this point in the history
Co-authored-by: Jimmy Jia <[email protected]>
  • Loading branch information
dzucconi and taion authored Jul 24, 2020
1 parent 78de701 commit 24c1a1e
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 11 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ const jsxRoute = (

#### Redirects

The `Redirect` route class sets up static redirect routes. You can also use it to create JSX `<Redirect>` elements for use with `makeRouteConfig`. This class takes `from` and `to` properties. `from` should be a path pattern as for normal routes above. `to` can be either a path pattern or a function. If it is a path pattern, the router will populate path parameters appropriately. If it is a function, it will receive the same routing state object as `getComponent` and `getData`, as described above.
The `Redirect` route class sets up static redirect routes. You can also use it to create JSX `<Redirect>` elements for use with `makeRouteConfig`. This class takes `from` and `to` properties and an optional `status` property. `from` should be a path pattern as for normal routes above. `to` can be either a path pattern or a function. If it is a path pattern, the router will populate path parameters appropriately. If it is a function, it will receive the same routing state object as `getComponent` and `getData`, as described above. `status` is used to set the HTTP status code when redirecting from the server, and defaults to `302` if it is not specified.

```js
const redirect1 = new Redirect({
Expand All @@ -499,6 +499,7 @@ const redirect1 = new Redirect({
const redirect2 = new Redirect({
from: 'widget/:widgetId',
to: ({ params }) => `/widgets/${params.widgetId}`,
status: 301,
});

const jsxRedirect1 = (
Expand All @@ -509,11 +510,12 @@ const jsxRedirect2 = (
<Redirect
from="widget/:widgetId"
to={({ params }) => `/widgets/${params.widgetId}`}
status={301}
/>
);
```

If you need more custom control over redirection, throw a `RedirectException` in your route's `render` method with a [location descriptor](https://github.com/4Catalyzer/farce#locations-and-location-descriptors) for the redirect destination.
If you need more custom control over redirection, throw a `RedirectException` in your route's `render` method with a [location descriptor](https://github.com/4Catalyzer/farce#locations-and-location-descriptors) and optional status code as above for the redirect destination.

```js
const customRedirect = {
Expand All @@ -524,6 +526,12 @@ const customRedirect = {
}
},
};

const permanentRedirect = {
render: () => {
throw new RedirectException('/widgets', 301);
},
};
```

#### Error handling
Expand Down Expand Up @@ -914,7 +922,7 @@ app.use(async (req, res) => {
});

if (redirect) {
res.redirect(302, redirect.url);
res.redirect(redirect.status, redirect.url);
return;
}

Expand Down Expand Up @@ -992,7 +1000,7 @@ app.use(async (req, res) => {
});
} catch (e) {
if (e.isFoundRedirectException) {
res.redirect(302, store.farce.createHref(e.location));
res.redirect(e.status, store.farce.createHref(e.location));
return;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/universal-redux/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ app.use(async (req, res) => {
});
} catch (e) {
if (e instanceof RedirectException) {
res.redirect(302, store.farce.createHref(e.location));
res.redirect(e.status, store.farce.createHref(e.location));
return;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/universal/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ app.use(async (req, res) => {
});

if (redirect) {
res.redirect(302, redirect.url);
res.redirect(redirect.status, redirect.url);
return;
}

Expand Down
7 changes: 4 additions & 3 deletions src/Redirect.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import RedirectException from './RedirectException';

export default class Redirect {
constructor({ from, to }) {
constructor({ from, to, status }) {
this.path = from;
this.to = to;
this.status = status;
}

render({ match }) {
const { to } = this;
const { to, status } = this;
let toLocation;

if (typeof to === 'function') {
Expand All @@ -17,7 +18,7 @@ export default class Redirect {
toLocation = router.matcher.format(to, params);
}

throw new RedirectException(toLocation);
throw new RedirectException(toLocation, status);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/RedirectException.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
export default class RedirectException {
isFoundRedirectException = true;

constructor(location) {
constructor(location, status = 302) {
this.location = location;
this.status = status;
}
}
1 change: 1 addition & 0 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export async function getFarceResult({
// The store is not exposed to the user, so we need to build the redirect
// URL here.
return {
status: e.status,
redirect: {
url: store.farce.createHref(e.location),
},
Expand Down
1 change: 1 addition & 0 deletions test/makeRouteConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ describe('makeRouteConfig', () => {

expect(redirectException).toBeInstanceOf(RedirectException);
expect(redirectException.location).toBe('/bar');
expect(redirectException.status).toBe(302);
});
});
});
45 changes: 45 additions & 0 deletions test/server/getFarceResult.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Redirect from '../../src/Redirect';
import RedirectException from '../../src/RedirectException';
import { getFarceResult } from '../../src/server';

Expand Down Expand Up @@ -85,6 +86,27 @@ describe('getFarceResult', () => {
});

it('should support redirects', async () => {
expect(
await getFarceResult({
url: '/foo',
routeConfig: [
new Redirect({
from: 'foo',
to: '/bar',
}),
],
}),
).toMatchInlineSnapshot(`
Object {
"redirect": Object {
"url": "/bar",
},
"status": 302,
}
`);
});

it('should support custom redirects', async () => {
expect(
await getFarceResult({
url: '/foo',
Expand All @@ -102,6 +124,29 @@ describe('getFarceResult', () => {
"redirect": Object {
"url": "/bar",
},
"status": 302,
}
`);
});

it('should support redirects with custom status code', async () => {
expect(
await getFarceResult({
url: '/foo',
routeConfig: [
new Redirect({
from: 'foo',
to: '/bar',
status: 301,
}),
],
}),
).toMatchInlineSnapshot(`
Object {
"redirect": Object {
"url": "/bar",
},
"status": 301,
}
`);
});
Expand Down
4 changes: 3 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ declare module 'found' {
interface RedirectProps {
from?: string;
to: string | ((match: Match) => LocationDescriptor);
status?: number;
}

class Redirect extends React.Component<RedirectProps> {}
Expand Down Expand Up @@ -448,8 +449,9 @@ declare module 'found' {
): React.ComponentType<Omit<Props, keyof RouterState>>;

class RedirectException {
constructor(location: LocationDescriptor);
constructor(location: LocationDescriptor, status?: number);
location: LocationDescriptor;
status: number;
}

/**
Expand Down
1 change: 1 addition & 0 deletions types/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ declare module 'found/server' {

interface FarceRedirectResult {
redirect: {
status: number;
url: string;
};
}
Expand Down

0 comments on commit 24c1a1e

Please sign in to comment.