Skip to content

Commit 4e1982b

Browse files
Added state to .push (#215)
* Added state to .push * Added state to replace and kept state in location * Added state to Link, replaceTo and pushedTo * Updated docs --------- Co-authored-by: Liam Ma <[email protected]>
1 parent 2e3973f commit 4e1982b

File tree

11 files changed

+232
-74
lines changed

11 files changed

+232
-74
lines changed

docs/api/components.md

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Router
22

3-
The `Router` component should ideally wrap your client app as high up in the tree as possible.
3+
The `Router` component should ideally wrap your client app as high up in the tree as possible.
44

55
If you are planning to render your application on the server, we recommend creating a composition boundary between your router and the core of your application, including your `RouteComponent`.
66

@@ -25,21 +25,25 @@ import { appRoutes } from './routing';
2525

2626
const resourcesPlugin = createResourcesPlugin({});
2727

28-
<Router history={createBrowserHistory()} routes={appRoutes} plugins={[resourcesPlugin]}>
28+
<Router
29+
history={createBrowserHistory()}
30+
routes={appRoutes}
31+
plugins={[resourcesPlugin]}
32+
>
2933
<App />
3034
</Router>;
3135
```
3236

3337
### Router props
3438

35-
| prop | type | description |
36-
| ----------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
37-
| `routes` | `Routes[]` | Your application's routes |
38-
| `history` | `History` | The history instance for the router, if omitted memory history will be used (optional but recommended) |
39-
| `plugins` | `Plugin[]` | Plugin allows you to hook into Router API and extra login on route load/prefetch/etc |
40-
| `basePath` | `string` | Base path string that will get prepended to all route paths (optional) |
41-
| `initialRoute` | `Route` | The route your application is initially showing, it's a performance optimisation to avoid route matching cost on initial render(optional) |
42-
| `onPrefetch` | `function(RouterContext)` | Called when prefetch is triggered from a Link (optional) |
39+
| prop | type | description |
40+
| -------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
41+
| `routes` | `Routes[]` | Your application's routes |
42+
| `history` | `History` | The history instance for the router, if omitted memory history will be used (optional but recommended) |
43+
| `plugins` | `Plugin[]` | Plugin allows you to hook into Router API and extra login on route load/prefetch/etc |
44+
| `basePath` | `string` | Base path string that will get prepended to all route paths (optional) |
45+
| `initialRoute` | `Route` | The route your application is initially showing, it's a performance optimisation to avoid route matching cost on initial render(optional) |
46+
| `onPrefetch` | `function(RouterContext)` | Called when prefetch is triggered from a Link (optional) |
4347

4448
## Resources plugin
4549

@@ -77,12 +81,11 @@ export const routes = [
7781

7882
### Resources plugin props
7983

80-
| prop | type | description |
81-
| ----------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
82-
| `context` | `ResourceContext` | Custom contextual data that will be provided to all your resources' `getKey` and `getData` methods (optional) |
83-
| `resourceData` | `ResourceData` | Pre-resolved resource data. When provided, the router will not request resources on mount (optional) |
84-
| `timeout` | `number` | `timout` is used to prevent slow APIs from causing long renders on the server, If a route resource does not return within the specified time then its data and promise will be set to null.(optional) |
85-
84+
| prop | type | description |
85+
| -------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86+
| `context` | `ResourceContext` | Custom contextual data that will be provided to all your resources' `getKey` and `getData` methods (optional) |
87+
| `resourceData` | `ResourceData` | Pre-resolved resource data. When provided, the router will not request resources on mount (optional) |
88+
| `timeout` | `number` | `timout` is used to prevent slow APIs from causing long renders on the server, If a route resource does not return within the specified time then its data and promise will be set to null.(optional) |
8689

8790
## MemoryRouter
8891

@@ -133,6 +136,7 @@ export const LinkExample = ({ href = '/' }) => {
133136
| `params` | `{ [key]: string }` | Used with `to` to generate correct path url |
134137
| `query` | `{ [key]: string }` | Used with `to` to generate correct query string url |
135138
| `prefetch` | `false` or `hover` or `mount` | Used to start prefetching router resources |
139+
| `state` | `unknown` | Allows you to pass state via location |
136140

137141
## Redirect
138142

@@ -254,10 +258,10 @@ Actions that communicate with the router's routing functionality are exposed saf
254258
By using either of these you will gain access to the following actions
255259

256260
| prop | type | arguments | description |
257-
| --------------- | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
258-
| `push` | `function` | `path: Href | Location, state?: any` | Calls `history.push` with the supplied args |
261+
| --------------- | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
262+
| `push` | `function` | `path: Href | Location, state?: any` | Calls `history.push` with the supplied args |
259263
| `pushTo` | `function` | `route: Route, attributes?: { params?: {}, query?: {} }` | Calls `history.push` generating the path from supplied route and attributes |
260-
| `replace` | `function` | `path: Href | Location, state?: any` | Calls `history.replace` with the supplied args |
264+
| `replace` | `function` | `path: Href | Location, state?: any` | Calls `history.replace` with the supplied args |
261265
| `replaceTo` | `function` | `route: Route, attributes?: { params?: {}, query?: {} }` | Calls `history.replace` generating the path from supplied route and attributes |
262266
| `goBack` | `function` | | Goes to the previous route in history |
263267
| `goForward` | `function` | | Goes to the next route in history |

docs/api/hooks.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { FeedRefresher } from './FeedRefresher';
1212
import { FeedClearance } from './FeedCleaner';
1313

1414
export const Feed = () => {
15-
const { data, loading, error, update, refresh, clear } = useResource(feedResource);
15+
const { data, loading, error, update, refresh, clear } =
16+
useResource(feedResource);
1617

1718
if (error) {
1819
return <Error error={error} />;
@@ -48,9 +49,10 @@ As well as returning actions that act on the resource (i.e. update and refresh),
4849

4950
Where `-prev-` indicates the field will remain unchanged from any previous state, possibly the inital state.
5051

51-
It is important to note
52-
* The timeout state is essentially a hung loading state, with the difference that `promise = null` and `error != null`. Developers should give priority to `loading` when deciding between loading or error states for their components. Promises/errors should only ever be thrown on the client.
53-
* The `promise` reflects the last operation, either async or explicit update. Update will clear `error`, set `data`. It will also set a `promise` consistent with that `data` so long as no async is `loading`. When `loading` the `promise` will always reflect the future `data` or `error` from the pending async.
52+
It is important to note
53+
54+
- The timeout state is essentially a hung loading state, with the difference that `promise = null` and `error != null`. Developers should give priority to `loading` when deciding between loading or error states for their components. Promises/errors should only ever be thrown on the client.
55+
- The `promise` reflects the last operation, either async or explicit update. Update will clear `error`, set `data`. It will also set a `promise` consistent with that `data` so long as no async is `loading`. When `loading` the `promise` will always reflect the future `data` or `error` from the pending async.
5456

5557
Additionaly `useResource` accepts additional arguments to customise behaviour, like `routerContext`.
5658
Check out [this section](../resources/usage.md) for more details on how to use the `useResource` hook.
@@ -71,6 +73,40 @@ export const MyRouteComponent = () => {
7173
};
7274
```
7375

76+
You can also use the `location` inside the `routerState` to access state passed via a `Link` or `push` from `routerActions`.
77+
78+
```js
79+
const StartPage = () => {
80+
const [routerState, routerActions] = useRouter();
81+
82+
const handleButtonClick = () => {
83+
const url = '/destination';
84+
const state = { referrer: 'StartPage' };
85+
routerActions.push(url, state);
86+
};
87+
88+
return (
89+
<div>
90+
<h1>Welcome to the Start Page</h1>
91+
<button onClick={handleButtonClick}>Go to Destination</button>
92+
</div>
93+
);
94+
};
95+
96+
const DestinationPage = () => {
97+
const [routerState] = useRouter();
98+
99+
const referrer = routerState.location?.state?.referrer;
100+
101+
return (
102+
<div>
103+
<h1>Welcome to the Destination Page</h1>
104+
{referrer && <p>You came from the {referrer}!</p>}
105+
</div>
106+
);
107+
};
108+
```
109+
74110
## createRouterSelector
75111
76112
If you are worried about `useRouter` re-rendering too much, you can create custom router hooks using selectors that will trigger a re-render only when the selector output changes.

src/common/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ export type Location = {
1616
pathname: string;
1717
search: string;
1818
hash: string;
19+
state?: unknown;
1920
};
2021

2122
export type BrowserHistory = (
2223
| Omit<History4, 'location' | 'go' | 'createHref' | 'push' | 'replace'>
2324
| Omit<History5, 'location' | 'go' | 'createHref' | 'push' | 'replace'>
2425
) & {
2526
location: Location;
26-
push: (path: string | Location) => void;
27-
replace: (path: string | Location) => void;
27+
push: (path: string | Location, state?: unknown) => void;
28+
replace: (path: string | Location, state?: unknown) => void;
2829
};
2930

3031
export type History = BrowserHistory;
@@ -135,6 +136,7 @@ export type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
135136
params?: MatchParams;
136137
query?: Query;
137138
prefetch?: false | 'hover' | 'mount';
139+
state?: unknown;
138140
};
139141

140142
export type HistoryBlocker = (

src/controllers/redirect/test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ describe('<Redirect />', () => {
7878
const to = '/cool-page';
7979

8080
expect(() => mountInRouter({ to })).not.toThrow();
81-
expect(MockHistory.push).toHaveBeenCalledWith(to);
81+
expect(MockHistory.push).toHaveBeenCalledWith(to, undefined);
8282
});
8383

8484
it("doesn't break / throw when rendered with location `to` created from string", () => {
8585
const to = '/go-out?search=foo#hash';
8686

8787
expect(() => mountInRouter({ to })).not.toThrow();
88-
expect(MockHistory.push).toHaveBeenCalledWith(to);
88+
expect(MockHistory.push).toHaveBeenCalledWith(to, undefined);
8989
});
9090

9191
it.each([
@@ -121,7 +121,7 @@ describe('<Redirect />', () => {
121121
"doesn't break / throw when rendered with `to` as a Route object, %s",
122122
(_, to, params, query, expected) => {
123123
expect(() => mountInRouter({ to, params, query })).not.toThrow();
124-
expect(MockHistory.push).toHaveBeenCalledWith(expected);
124+
expect(MockHistory.push).toHaveBeenCalledWith(expected, undefined);
125125
}
126126
);
127127

@@ -132,7 +132,7 @@ describe('<Redirect />', () => {
132132
'should navigate to given route %s correctly',
133133
(_, to, params, query, expected) => {
134134
mountInRouter({ to, query, params, push: false });
135-
expect(MockHistory.replace).toHaveBeenCalledWith(expected);
135+
expect(MockHistory.replace).toHaveBeenCalledWith(expected, undefined);
136136
expect(MockHistory.push).not.toHaveBeenCalled();
137137
}
138138
);
@@ -168,7 +168,7 @@ describe('<Redirect />', () => {
168168
'should use push history correctly with given route %s',
169169
(_, to, params, expected) => {
170170
mountInRouter({ to, params, push: true });
171-
expect(MockHistory.push).toHaveBeenCalledWith(expected);
171+
expect(MockHistory.push).toHaveBeenCalledWith(expected, undefined);
172172
expect(MockHistory.replace).not.toHaveBeenCalled();
173173
}
174174
);

src/controllers/router-actions/test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ describe('<RouterActions />', () => {
9898
</Router>
9999
);
100100

101-
expect(HistoryMock.push).toBeCalledWith('push');
102-
expect(HistoryMock.replace).toBeCalledWith('replace');
101+
expect(HistoryMock.push).toBeCalledWith('push', undefined);
102+
expect(HistoryMock.replace).toBeCalledWith('replace', undefined);
103103
expect(HistoryMock.goBack).toBeCalled();
104104
expect(HistoryMock.goForward).toBeCalled();
105105
expect(HistoryMock.block).toHaveBeenCalledWith(blockCallback);

src/controllers/router-store/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ const actions: AllRouterActions = {
174174
},
175175

176176
push:
177-
path =>
177+
(path, state) =>
178178
({ getState }) => {
179179
const { history, basePath } = getState();
180180
if (isExternalAbsolutePath(path)) {
181181
window.location.assign(path as string);
182182
} else {
183-
history.push(getRelativePath(path, basePath));
183+
history.push(getRelativePath(path, basePath), state);
184184
}
185185
},
186186

@@ -198,17 +198,17 @@ const actions: AllRouterActions = {
198198
attributes.query,
199199
basePath
200200
);
201-
history.push(location as any);
201+
history.push(location as any, attributes?.state);
202202
},
203203

204204
replace:
205-
path =>
205+
(path, state) =>
206206
({ getState }) => {
207207
const { history, basePath } = getState();
208208
if (isExternalAbsolutePath(path)) {
209209
window.location.replace(path as string);
210210
} else {
211-
history.replace(getRelativePath(path, basePath) as any);
211+
history.replace(getRelativePath(path, basePath) as any, state);
212212
}
213213
},
214214

@@ -226,7 +226,7 @@ const actions: AllRouterActions = {
226226
attributes.query,
227227
basePath
228228
);
229-
history.replace(location as any);
229+
history.replace(location as any, attributes?.state);
230230
},
231231

232232
goBack:

0 commit comments

Comments
 (0)