Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved onError handling #7

Merged
merged 12 commits into from
Oct 18, 2016
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 57 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,63 @@ isAborted Function that returns if the hooks has been aborted, can be us

### Default props available to decorated components
```
loading Will be true when blocking hooks are not yet completed
deferredLoading Will be true when deferred hooks are not yet completed
reload Function that can be invoked to re-trigger the hooks for the current component
abort Function that can be invoked to abort current running hooks
loading Will be true when beforeTransition hooks are not yet completed
afterTransitionLoading Will be true when afterTransition hooks are not yet completed
reload Function that can be invoked to re-trigger the hooks for the current component
abort Function that can be invoked to abort current running hooks
```
Additionally components will have access to properties that has been set using `setProps`.

## Client API
The custom redial router middleware `useRedial` makes it easy to add support for redial on the client side using the `render` property from `Router` in React Router. It provides the following properties as a way to configure how the data loading should behave.
```
locals Extra locals that should be provided to the hooks other than the default ones
blocking Hooks that should be completed before a route transition is completed
defer Hooks that are not needed before making a route transition
parallel If set to true the deferred hooks will run in parallel with the blocking ones
initialLoading Component should be shown on initial client load, useful if server rendering is not used
onStarted(force) Invoked when a route transition has been detected and when redial hooks will be invoked
onError(error, type) Invoked when an error happens, type can be either a "location-change", "aborted" or "other" reason
onAborted Invoked if it was prematurely aborted through manual interaction
onCompleted Invoked if everything was completed successfully, both blocking and deferred
locals Extra locals that should be provided to the hooks other than the default ones
beforeTransition Hooks that should be completed before a route transition is completed
afterTransition Hooks that are not needed before making a route transition
parallel If set to true the afterTransition hooks will run in parallel with the beforeTransition ones
initialLoading Component should be shown on initial client load, useful if server rendering is not used
onStarted(force) Invoked when a route transition has been detected and when redial hooks will be invoked
onError(error, metaData) Invoked when an error happens, see below for more info
onAborted(becauseError) Invoked if it was prematurely aborted through manual interaction or an error
onCompleted(type) Invoked if everything was completed successfully, with type being either "beforeTransition" or "afterTransition"
```

### `onError(error, metaData)`
__`metaData`__
```
abort() Function that can be used to abort current loading
beforeTransition If the error originated from a beforeTransition hook or not
reason The reason for the error, can be either a "location-changed", "aborted" or "other"
router React Router instance https://github.com/ReactTraining/react-router/blob/master/docs/API.md#contextrouter
```

#### Example
We can use `onError` to add handling for errors in our application. The example below shows how we can make the client either reload the page or transition back to the previous page on an error.

```javascript
const forcePageReloadOnError = true;
const goBackOnError = false;

// Function that can be used as a setting for useRedial
function onError(err, { abort, beforeTransition, reason, router }) {
if (process.env.NODE_ENV !== 'production') {
console.error(reason, err);
}

// We only what to do this if it was a beforeTransition hook that failed
if (beforeTransition) {
if (forcePageReloadOnError && reason === 'other') {
window.location.reload();
} else if (goBackOnError && reason !== 'location-changed') {
router.goBack();
}
// Abort current loading automatically
abort();
}
}
```

### Example
```js
import { useRedial } from 'react-router-redial';
import { applyRouterMiddleware } from 'react-router';
Expand All @@ -95,10 +131,10 @@ import { applyRouterMiddleware } from 'react-router';
history={ browserHistory }
routes={ routes }
render={ applyRouterMiddleware(
useRedial({
useRedial({
locals,
blocking: ['fetch'],
defer: ['defer', 'done'],
beforeTransition: ['fetch'],
afterTransition: ['defer', 'done'],
parallel: true,
initialLoading: () => <div>Loading…</div>,
})
Expand All @@ -121,6 +157,7 @@ redialMap This should be used together with RedialContext on the server
redialProps This is for passing the props that has been defined with setProps to the client, expected to be on window.__REDIAL_PROPS__
```

### Example
```js
import { triggerHooks } from 'react-router-redial';

Expand All @@ -138,7 +175,7 @@ triggerHooks({
```

## Hooks
react-router-redial provides a simple way to define in what order certain hooks should run and if they can run in parallel. The same syntax is used for both `hooks` when used on the server with `triggerHooks` and `blocking` + `defer` on the client with `RedialContext`. The hooks are expected to be an array the can contain either single hooks or arrays of hooks. Each individual element in the array will run in parallel and after it has been completed the next element will be managed. This means that you can run some hooks together and others after they have been completed. This is useful if you for instance want to run some hook that should have access to some data that other hooks before it should have defined.
react-router-redial provides a simple way to define in what order certain hooks should run and if they can run in parallel. The same syntax is used for both `hooks` when used on the server with `triggerHooks` and `beforeTransition` + `afterTransition` on the client with `RedialContext`. The hooks are expected to be an array the can contain either single hooks or arrays of hooks. Each individual element in the array will run in parallel and after it has been completed the next element will be managed. This means that you can run some hooks together and others after they have been completed. This is useful if you for instance want to run some hook that should have access to some data that other hooks before it should have defined.

### Example
Let's look at an example to understand this a bit better. Say that we have the following hooks defined on the server:
Expand Down Expand Up @@ -176,10 +213,10 @@ export default (container, store) => {
history={browserHistory}
routes={routes}
render={ applyRouterMiddleware(
useRedial({
useRedial({
locals,
blocking: ['fetch'],
defer: ['defer', 'done'],
beforeTransition: ['fetch'],
afterTransition: ['defer', 'done'],
parallel: true,
initialLoading: () => <div>Loading…</div>,
})
Expand Down
4 changes: 2 additions & 2 deletions examples/redux/render/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export default (container, routes, store) => {
routes={routes}
render={applyRouterMiddleware(useRedial({
locals,
blocking: ['fetch', 'blockingDone'],
defer: ['defer', 'deferDone'],
beforeTransition: ['fetch', 'blockingDone'],
afterTransition: ['defer', 'deferDone'],
parallel: false,
}))}
/>
Expand Down
1 change: 1 addition & 0 deletions examples/redux/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ server.use(function *() {
} catch (e) {
if (e) {
this.status = 500;
console.error(e);
} else {
this.status = 404;
}
Expand Down
6 changes: 3 additions & 3 deletions examples/simple-with-scroll/render/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export default (container, routes) => {
history={browserHistory}
routes={routes}
render={applyRouterMiddleware(
useScroll(),
useScroll(),
useRedial({
blocking: ['fetch'],
defer: ['defer', 'done'],
beforeTransition: ['fetch'],
afterTransition: ['defer', 'done'],
parallel: false,
initialLoading: () => <div>Loading…</div>,
})
Expand Down
1 change: 1 addition & 0 deletions examples/simple-with-scroll/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ server.use(function *() {
} catch (e) {
if (e) {
this.status = 500;
console.error(e);
} else {
this.status = 404;
}
Expand Down
6 changes: 6 additions & 0 deletions examples/simple/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export default class App extends Component {
<li>
<Link to="/github">Github</Link>
</li>
<li>
<Link to="/fetch">Fetch, with client error</Link>
</li>
<li>
<Link to="/defer">Defer, with client error</Link>
</li>
</ul>
{this.props.children}
</div>
Expand Down
29 changes: 29 additions & 0 deletions examples/simple/components/Defer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { Component } from 'react';
import { provideHooks } from 'redial';

@provideHooks({
defer: ({ setProps, getProps }) => new Promise((resolve) => {
const { color } = getProps();
if(!color) {
if (typeof window === 'object') {
throw new Error('Defer: Demo error on client side only');
}
setTimeout(() => {
const getValue = () => Math.round(Math.random() * 255);
setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`});
resolve();
}, 1000);
} else {
resolve();
}
})
})
export default class Fetch extends Component {
render() {
return (
<div>
<h1 style={{ color: this.props.color }}>Client will get error on blocking</h1>
</div>
);
}
}
29 changes: 29 additions & 0 deletions examples/simple/components/Fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { Component } from 'react';
import { provideHooks } from 'redial';

@provideHooks({
fetch: ({ setProps, getProps }) => new Promise((resolve) => {
const { color } = getProps();
if(!color) {
if (typeof window === 'object') {
throw new Error('Fetch: Demo error on client side only');
}
setTimeout(() => {
const getValue = () => Math.round(Math.random() * 255);
setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`});
resolve();
}, 1000);
} else {
resolve();
}
})
})
export default class Fetch extends Component {
render() {
return (
<div>
<h1 style={{ color: this.props.color }}>Client will get error on blocking</h1>
</div>
);
}
}
28 changes: 25 additions & 3 deletions examples/simple/render/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,37 @@ import { Router, browserHistory, applyRouterMiddleware } from 'react-router';

// Render the app client-side to a given container element:
export default (container, routes) => {
const forcePageReloadOnError = true;
const goBackOnError = false;

// Function that can be used as a setting for useRedial
function onError(err, { abort, beforeTransition, reason, router }) {
if (process.env.NODE_ENV !== 'production') {
console.error(reason, err);
}

// We only what to do this if it was a blocking hook that failed
if (beforeTransition) {
if (forcePageReloadOnError && reason === 'other') {
window.location.reload();
} else if (goBackOnError && reason !== 'location-changed') {
router.goBack();
}
// Abort current loading automatically
abort();
}
}

const component = (
<Router
history={browserHistory}
routes={routes}
render={applyRouterMiddleware(useRedial({
blocking: ['fetch'],
defer: ['defer', 'done'],
parallel: false,
beforeTransition: ['fetch'],
afterTransition: ['defer', 'done'],
parallel: true,
initialLoading: () => <div>Loading…</div>,
onError,
}))}
/>
);
Expand Down
4 changes: 4 additions & 0 deletions examples/simple/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import App from './components/App';
import Index from './components/Index';
import Github from './components/Github';
import User from './components/User';
import Fetch from './components/Fetch';
import Defer from './components/Defer';

export default (
<Route path="/" component={App}>
<IndexRoute component={Index}/>
<Route path="github" component={Github}>
<Route path="user/:id" component={User} />
</Route>
<Route path="fetch" component={Fetch} />
<Route path="defer" component={Defer} />
</Route>
)
1 change: 1 addition & 0 deletions examples/simple/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ server.use(function *() {
} catch (e) {
if (e) {
this.status = 500;
console.error(e);
} else {
this.status = 404;
}
Expand Down
12 changes: 7 additions & 5 deletions src/RedialContextContainer.js → src/RedialContainer.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import React, { Component, PropTypes } from 'react';
import getRoutePath from './util/getRoutePath';

export default class RedialContextContainer extends React.Component {
export default class RedialContainer extends Component {
static displayName = 'RedialContainer';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want the component to not be called something like t in React devtools.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


static propTypes = {
children: React.PropTypes.element.isRequired,
routerProps: React.PropTypes.object.isRequired,
children: PropTypes.element.isRequired,
routerProps: PropTypes.object.isRequired,
};

static contextTypes = {
redialContext: React.PropTypes.object.isRequired,
redialContext: PropTypes.object.isRequired,
};

render() {
Expand Down
Loading