Skip to content

Commit 6dabf84

Browse files
Consolidate Router component
1 parent 8491e86 commit 6dabf84

File tree

33 files changed

+410
-1160
lines changed

33 files changed

+410
-1160
lines changed

docs/_sidebar.md

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
* **Router**
1212

1313
- [Configuration](/router/configuration.md)
14-
- [Selection](/router/selection.md)
1514
- [State](/router/state.md)
1615
- [SSR](/router/ssr.md)
1716

docs/api/components.md

+13-61
Original file line numberDiff line numberDiff line change
@@ -29,82 +29,34 @@ import { appRoutes } from './routing';
2929

3030
### Router props
3131

32-
| prop | type | description |
33-
| ----------------- | ------------------------- | -------------------------------------------------------------------------------------------------- |
34-
| `routes` | `Routes[]` | Your application's routes |
35-
| `initialRoute` | `Route` | The route your application is initially showing |
36-
| `history` | `History` | The history instance for the router |
37-
| `basePath` | `string` | Base path string that will get prepended to all route paths |
38-
| `resourceContext` | `ResourceContext` | Custom contextual data that will be provided to all your resources' `getKey` and `getData` methods |
39-
| `resourceData` | `ResourceData` | Pre-resolved resource data. When provided, the router will not request resources on mount |
40-
| `onPrefetch` | `function(RouterContext)` | Called when prefetch is triggered from a Link |
41-
42-
## StaticRouter
43-
44-
If you are planning to render your application on the server, you must use the `StaticRouter` in your server side entry. The `StaticRouter` should only be used on server as it omits all browser-only resources. It does not require a `history` prop to be provided, instead, you simply need to provide the current `location` as a string. In order to achieve this, we recommend your server side application uses [`jsdom`](https://github.com/jsdom/jsdom).
45-
46-
```js
47-
// server-app.js
48-
import { StaticRouter } from 'react-resource-router';
49-
import { App } from '../components';
50-
import { appRoutes } from '../routing';
51-
52-
const { pathname, search } = window.location;
53-
const location = `${pathname}${search}`;
54-
55-
export const ServerApp = () => (
56-
<StaticRouter routes={appRoutes} location={location}>
57-
<App />
58-
</StaticRouter>
59-
);
60-
```
61-
62-
### StaticRouter props
63-
64-
| prop | type | description |
65-
| ---------- | ---------- | ----------------------------------------------------------- |
66-
| `routes` | `Routes[]` | Your application's routes |
67-
| `location` | `string` | The string representation of the app's current location |
68-
| `basePath` | `string` | Base path string that will get prepended to all route paths |
32+
| prop | type | description |
33+
| ----------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
34+
| `routes` | `Routes[]` | Your application's routes |
35+
| `history` | `History` | The history instance for the router, if omitted memory history will be used (optional but recommended) |
36+
| `location` | `string` | If `history` prop is omitted, this configures the initial location for the default memory history (optional, useful for tests and storybooks) |
37+
| `basePath` | `string` | Base path string that will get prepended to all route paths (optional) |
38+
| `initialRoute` | `Route` | The route your application is initially showing, it's a performance optimisation to avoid route matching cost on initial render(optional) |
39+
| `resourceContext` | `ResourceContext` | Custom contextual data that will be provided to all your resources' `getKey` and `getData` methods (optional) |
40+
| `resourceData` | `ResourceData` | Pre-resolved resource data. When provided, the router will not request resources on mount (optional) |
41+
| `onPrefetch` | `function(RouterContext)` | Called when prefetch is triggered from a Link (optional) |
6942

7043
## MemoryRouter
7144

7245
The `MemoryRouter` component can be used for your application's unit tests.
7346

7447
```js
7548
it('should send right props after render with routes', () => {
76-
mount(
77-
<MemoryRouter routes={[mockRoutes[0]]}>
78-
<RouterSubscriber>
79-
{({ history, location, routes, route, match, query }) => {
80-
expect(history).toEqual(mockHistory);
81-
expect(location).toEqual(mockLocation);
82-
expect(routes).toEqual(routes);
83-
expect(route).toEqual(
84-
expect.objectContaining({
85-
path: `/pathname`,
86-
})
87-
);
88-
expect(match).toBeTruthy();
89-
expect(query).toEqual({
90-
foo: 'bar',
91-
});
92-
93-
return <div>I am a subscriber</div>;
94-
}}
95-
</RouterSubscriber>
96-
</MemoryRouter>
97-
);
49+
render(<MemoryRouter routes={[mockRoutes[0]]}>{/* ... */}</MemoryRouter>);
9850
});
9951
```
10052

10153
### MemoryRouter props
10254

10355
| prop | type | description |
10456
| ---------- | ---------- | ----------------------------------------------------------- |
105-
| `routes` | `Routes[]` | Your application's routes |
106-
| `location` | `string` | The string representation of the app's current location |
10757
| `basePath` | `string` | Base path string that will get prepended to all route paths |
58+
| `location` | `string` | The string representation of the app's current location |
59+
| `routes` | `Routes[]` | Your application's routes |
10860

10961
## Link component
11062

docs/router/README.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
- **Router**
22

33
- [Configuration](./configuration.md)
4-
- [Selection](./selection.md)
54
- [State](./state.md)
65
- [SSR](./ssr.md)

docs/router/selection.md

-3
This file was deleted.

docs/router/ssr.md

+17-12
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,25 @@ import { RouteComponent } from 'react-resource-router';
2222

2323
export const App = () => (
2424
<>
25+
<Navigation />
2526
<RouteComponent />
27+
<Footer />
2628
</>
2729
);
2830
```
2931

30-
The reason for this is that currently, you will need to use the [`Router`](#router-component) component on the client and the [`StaticRouter`](#staticrouter-component) component on the server. Following the above composition pattern will allow you to use the correct router in your server side entry and client side entry respectively. This could look something like the following examples:
32+
When however you need to SSR your app, we need pass different props to Router, as `createBrowserHistory` does not really work on server, so we recommend to use `location` instead (or pass your own `MemoryHistory` if needed)
3133

3234
```js
3335
// server-app.js
34-
import { StaticRouter } from 'react-resource-router';
36+
import { Router } from 'react-resource-router';
3537
import { App } from '../components';
38+
import { routes } from '../routing/routes';
3639

37-
export const ServerApp = ({ location, routes }) => (
38-
<StaticRouter routes={routes} location={location}>
40+
export const ServerApp = ({ location }) => (
41+
<Router routes={routes} location={location}>
3942
<App />
40-
</StaticRouter>
43+
</Router>
4144
);
4245
```
4346

@@ -47,8 +50,10 @@ import { Router, createBrowserHistory } from 'react-resource-router';
4750
import { App } from '../components';
4851
import { routes } from '../routing/routes';
4952

53+
const history = createBrowserHistory();
54+
5055
export const ClientApp = () => (
51-
<Router routes={routes} history={createBrowserHistory()}>
56+
<Router routes={routes} history={history}>
5257
<App />
5358
</Router>
5459
);
@@ -58,21 +63,21 @@ export const ClientApp = () => (
5863

5964
Until React Suspense works on the server, we cannot do progressive rendering server side. To get around this, we need to `await` all resource requests to render our app _with all our resource data_ on the server.
6065

61-
Luckily the `StaticRouter` provides a convenient static method to do this for us.
66+
Luckily the `Router` provides a convenient static method to do this for us.
6267

6368
```js
6469
import { renderToString } from 'react-dom/server';
65-
import { StaticRouter } from 'react-resource-router';
70+
import { Router } from 'react-resource-router';
6671
import { routes } from '../routing/routes';
6772
import { ServerApp } from './app';
6873

6974
const renderToStringWithData = async ({ location }) => {
70-
await StaticRouter.requestResources({ location, routes });
75+
await Router.requestResources({ location, routes });
7176

72-
return renderToString(<ServerApp routes={routes} location={location} />);
77+
return renderToString(<ServerApp location={location} />);
7378
};
7479
```
7580

76-
Notice that we do not need to provide any `resourceData` object to the `ServerApp`, the `StaticRouter` handles this for us internally.
81+
Notice that we do not need to provide any `resourceData` object to the `ServerApp`, the `Router` handles this for us internally.
7782

78-
To prevent slow APIs from causing long renders on the server you can optionally pass in `timeout` as an option to `StaticRouter.requestResources`. If a route resource does not return within the specified time then its data and promise will be set to null.
83+
To prevent slow APIs from causing long renders on the server you can optionally pass in `timeout` as an option to `Router.requestResources`. If a route resource does not return within the specified time then its data and promise will be set to null.

examples/hydration/index.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
Router,
77
RouteComponent,
88
createBrowserHistory,
9-
StaticRouter,
109
} from 'react-resource-router';
1110

1211
import { homeRoute } from './routes';
@@ -16,9 +15,7 @@ const myHistory = createBrowserHistory();
1615
const appRoutes = [homeRoute];
1716

1817
const getStateFromServer = async () => {
19-
// StaticRouter should only be used on Server!
20-
// It's used in Browser in this example for simplicity.
21-
const resourceData = await StaticRouter.requestResources({
18+
const resourceData = await Router.requestResources({
2219
location: '/',
2320
routes: appRoutes,
2421
});

src/__tests__/integration/test.tsx

+13-10
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import { mount } from 'enzyme';
44
import * as historyHelper from 'history';
55
import { defaultRegistry } from 'react-sweet-state';
66

7-
import { Router, RouterActions, StaticRouter } from '../../controllers';
8-
import { RouteComponent } from '../../ui';
9-
import { RouterActionsType } from '../../controllers/router-store/types';
107
import { mockRoute } from '../../common/mocks';
8+
import { isServerEnvironment } from '../../common/utils/is-server-environment';
9+
import { Router, RouterActions } from '../../controllers';
1110
import { ResourceStore } from '../../controllers/resource-store';
11+
import type { RouterActionsType } from '../../controllers/router-store/types';
12+
import { RouteComponent } from '../../ui';
13+
14+
jest.mock('../../common/utils/is-server-environment');
1215

1316
const mockLocation = {
1417
pathname: '/projects/123/board/456',
@@ -57,6 +60,7 @@ describe('<Router /> integration tests', () => {
5760
history = historyHelper.createMemoryHistory(historyBuildOptions);
5861
historyPushSpy = jest.spyOn(history, 'push');
5962
historyReplaceSpy = jest.spyOn(history, 'replace');
63+
(isServerEnvironment as any).mockReturnValue(false);
6064
});
6165

6266
afterEach(() => {
@@ -137,8 +141,7 @@ describe('<Router /> integration tests', () => {
137141
},
138142
];
139143

140-
const serverData = await StaticRouter.requestResources({
141-
// @ts-ignore
144+
const serverData = await Router.requestResources({
142145
routes: mockedRoutes,
143146
location: mockLocation.pathname,
144147
timeout: 350,
@@ -228,7 +231,7 @@ describe('<Router /> integration tests', () => {
228231
});
229232
});
230233

231-
describe('<StaticRouter /> integration tests', () => {
234+
describe('<Router /> SSR-like integration tests', () => {
232235
const basePath = '/base';
233236
const route = {
234237
path: '/anotherpath',
@@ -238,23 +241,23 @@ describe('<StaticRouter /> integration tests', () => {
238241

239242
it('should match the right route when basePath is set', async () => {
240243
const wrapper = mount(
241-
<StaticRouter
244+
<Router
242245
routes={[route]}
243246
location={`${basePath}${route.path}`}
244247
basePath={basePath}
245248
>
246249
<RouteComponent />
247-
</StaticRouter>
250+
</Router>
248251
);
249252

250253
expect(wrapper.text()).toBe('important');
251254
});
252255

253256
it('should match the right route when basePath is not set', async () => {
254257
const wrapper = mount(
255-
<StaticRouter routes={[route]} location={route.path}>
258+
<Router routes={[route]} location={route.path}>
256259
<RouteComponent />
257-
</StaticRouter>
260+
</Router>
258261
);
259262

260263
expect(wrapper.text()).toBe('important');

src/__tests__/unit/controllers/memory-router/test.tsx

-21
This file was deleted.

src/__tests__/unit/controllers/resource-store/test.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React from 'react';
55
import { mount } from 'enzyme';
66
import { BoundActions, defaultRegistry } from 'react-sweet-state';
77

8+
import { isServerEnvironment } from '../../../../common/utils/is-server-environment';
89
import { useResource } from '../../../../controllers/hooks';
910
import { getResourceStore } from '../../../../controllers/resource-store';
1011
import { BASE_DEFAULT_STATE_SLICE } from '../../../../controllers/resource-store/constants';
@@ -24,6 +25,8 @@ import {
2425
import { createResource } from '../../../../controllers/resource-utils';
2526
import * as routerStoreModule from '../../../../controllers/router-store';
2627

28+
jest.mock('../../../../common/utils/is-server-environment');
29+
2730
jest.mock('../../../../controllers/resource-store/utils', () => ({
2831
...jest.requireActual<any>('../../../../controllers/resource-store/utils'),
2932
shouldUseCache: jest.fn(),
@@ -389,16 +392,18 @@ describe('resource store', () => {
389392
});
390393

391394
describe('requestResources', () => {
392-
it('should skip isBrowserOnly resources if isStatic is true', () => {
395+
it('should skip isBrowserOnly resources if server environment', () => {
396+
(isServerEnvironment as any).mockReturnValue(true);
393397
const data = actions.requestResources(
394398
[{ ...mockResource, isBrowserOnly: true }],
395399
mockRouterStoreContext,
396-
{ ...mockOptions, isStatic: true }
400+
mockOptions
397401
);
398402

399403
expect(data).toEqual([]);
400404
});
401-
it('should ignore isBrowserOnly if isStatic is falsey', async () => {
405+
it('should ignore isBrowserOnly if not server environment', async () => {
406+
(isServerEnvironment as any).mockReturnValue(false);
402407
(getDefaultStateSlice as any).mockImplementation(() => ({
403408
...BASE_DEFAULT_STATE_SLICE,
404409
expiresAt: 1,

0 commit comments

Comments
 (0)