Skip to content

Commit f32ba57

Browse files
Add isBrowserOnly to Resource and treat null as valid resource data (#107)
* Add isBrowser to Resource and treat null as valid resource data * for test 'should ignore isBrowser if isStatic is falsey', check store state after requestResources * change 'isBrowser' to 'isBrowserOnly' * Update doc * add clear method to clear resource and update documentation * update Flow type and rename DEFAULT_RESOURCE_IS_BROWSER to DEFAULT_RESOURCE_BROWSER_ONLY * add clearAll method and update documentation Co-authored-by: Liam Ma <[email protected]>
1 parent 3813611 commit f32ba57

File tree

27 files changed

+380
-26
lines changed

27 files changed

+380
-26
lines changed

docs/api/components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { appRoutes } from './routing';
4141

4242
## StaticRouter
4343

44-
If you are planning to render your application on the server, you must use the `StaticRouter` in your server side entry. The `StaticRouter` 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).
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).
4545

4646
```js
4747
// server-app.js

docs/api/hooks.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import { Loading, Error } from './primitives';
99
import { FeedList } from './FeedList';
1010
import { FeedUpdater } from './FeedUpdater';
1111
import { FeedRefresher } from './FeedRefresher';
12+
import { FeedClearance } from './FeedCleaner';
1213

1314
export const Feed = () => {
14-
const { data, loading, error, update, refresh } = useResource(feedResource);
15+
const { data, loading, error, update, refresh, clear } = useResource(feedResource);
1516

1617
if (error) {
1718
return <Error error={error} />;
@@ -26,6 +27,7 @@ export const Feed = () => {
2627
<FeedList items={data} />
2728
<FeedUpdater onUpdate={update} />
2829
<FeedRefresher onRefresh={refresh} />
30+
<FeedClearance onClear={clear} />
2931
</>
3032
);
3133
};

docs/resources/creation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ export const userProfileResource = createResource({
2121
| `getData` | `(routerExtendedContext, customContext) => Promise<any>` | This function is used to load the data for the resource. The function should return a promise and resolve with the resource data object. NOTE: You may not use `getData` and `getDataLoader` on the same resource |
2222
| `maxAge` | `number` | How long (in milliseconds) the resource should be kept in the router before a fresh resource is retrieved. Note: resources are only refreshed on route _change_. The router does not poll or update resources in the background. Navigation within the same route, e.g. query param change, will not trigger a refresh of resources. |
2323
| `maxCache` | `number` | How many resources can be kept in the router for a particular `type`. Once the threshold limit is reached, a **Least Recently** used resource gets deleted, making space for the new requested resource of the same type. |
24-
| `getDataLoader` | `() => Promise<{default: getData}>` | Optional property that enables neater code splitting. See more below. NOTE: You may not use `getData` and `getDataLoader` on the same resource |
24+
| `getDataLoader` | `() => Promise<{default: getData}>` | Optional property that enables neater code splitting. See more below. NOTE: You may not use `getData` and `getDataLoader` on the same resource
25+
| `isBrowserOnly` | `boolean` | Optional property that skips fetching the resource on server. The default value is false. |

docs/resources/interaction.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,22 @@ export const UsernameResetter = ({ newUsername }) => {
4848
);
4949
};
5050
```
51+
52+
## Clearing
53+
54+
The `clear` method is bound to the resource that you provide to [`useResource`](../api/hooks.md#useresource) hook or the [`ResourceSubscriber`](../api/components.md#resourcesubscriber). Calling this function will clear the resource so that the resource will be fetched from remote next time it's needed.
55+
56+
```js
57+
import { useResource } from 'react-resource-router';
58+
import { accountInfoResource } from '../routing/resources';
59+
60+
export const UsernameResetter = ({ newUsername }) => {
61+
const { data, clear } = useResource(accountInfoResource);
62+
63+
return (
64+
<button onClick={() => clear()}>
65+
Clear your username
66+
</button>
67+
);
68+
};
69+
```

docs/resources/usage.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Resources expose properties and functions via the [`useResource`](../api/hooks.m
99
| `error` | `error` or `null` | If your getData function throws an error, it will be stored here |
1010
| `update` | `function` | Allows you to imperatively update the resource's current state bypassing its `maxAge` |
1111
| `refresh` | `function` | Allows you to imperatively refresh the resource's state by calling its `getData` method |
12+
| `clear` | `function` | Allows you to imperatively clear the resource's state |
13+
| `clearAll`| `function` | Clears all resource data of the particular type regardless of its keys |
1214
| `key` | `string` | Unique key for the resource |
1315

1416
You can use these properties and functions to implement your own customised render logic inside your resource consuming components.

examples/hydration/home.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { createResource, useResource } from 'react-resource-router';
3+
4+
export const homeResource = createResource({
5+
type: 'home',
6+
getKey: () => 'breedList',
7+
maxAge: 10000,
8+
getData: async () => {
9+
const response = await fetch('https://dog.ceo/api/breeds/image/random');
10+
const result: { message: string } = await response.json();
11+
12+
return result;
13+
},
14+
isBrowserOnly: true,
15+
});
16+
17+
export const Home = () => {
18+
// eslint-disable-next-line
19+
const { data, loading, error } = useResource(homeResource);
20+
21+
return (
22+
<div>
23+
<h1>Random Dog</h1>
24+
<section>
25+
{data?.message && <img style={{ width: '400px' }} src={data.message} />}
26+
</section>
27+
</div>
28+
);
29+
};

examples/hydration/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Example - Hydration</title>
5+
</head>
6+
7+
<body>
8+
<div id="root"></div>
9+
<script src="bundle.js" type="text/javascript"></script>
10+
</body>
11+
</html>

examples/hydration/index.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { defaultRegistry } from 'react-sweet-state';
4+
5+
import {
6+
Router,
7+
RouteComponent,
8+
createBrowserHistory,
9+
StaticRouter,
10+
} from 'react-resource-router';
11+
12+
import { homeRoute } from './routes';
13+
14+
const myHistory = createBrowserHistory();
15+
16+
const appRoutes = [homeRoute];
17+
18+
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({
22+
location: '/',
23+
routes: appRoutes,
24+
});
25+
26+
// clearing the store
27+
defaultRegistry.stores.clear();
28+
29+
return resourceData;
30+
};
31+
32+
const main = async () => {
33+
const data = await getStateFromServer();
34+
35+
const App = () => {
36+
return (
37+
<Router
38+
routes={appRoutes}
39+
history={myHistory}
40+
basePath="/hydration"
41+
resourceData={data}
42+
>
43+
<RouteComponent />
44+
</Router>
45+
);
46+
};
47+
48+
ReactDOM.render(<App />, document.getElementById('root'));
49+
};
50+
51+
main();

examples/hydration/routes.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Home, homeResource } from './home';
2+
3+
export const homeRoute = {
4+
name: 'home',
5+
path: '/',
6+
exact: true,
7+
component: Home,
8+
navigation: null,
9+
resources: [homeResource],
10+
};

src/__tests__/integration/test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const mockResource = {
3838
getData: () => Promise.resolve('mock-data'),
3939
maxAge: 0,
4040
maxCache: Infinity,
41+
isBrowserOnly: false,
4142
};
4243

4344
const historyBuildOptions = {

0 commit comments

Comments
 (0)