Skip to content

Commit 0909404

Browse files
committed
adding storybook for better dev exp
also adding an error boundry around default (init required) props.
1 parent 344cd0c commit 0909404

File tree

10 files changed

+1583
-108
lines changed

10 files changed

+1583
-108
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ npm-debug.log*
88
.DS_Store
99
/keygen/key.p8
1010
/src/dist
11+
/devToken.js

.storybook/addons.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import '@storybook/addon-actions/register'
2+
import '@storybook/addon-links/register'
3+
import '@storybook/addon-knobs/register'

.storybook/config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { configure, addDecorator } from '@storybook/react'
2+
import { withKnobs } from '@storybook/addon-knobs/react'
3+
4+
// automatically import all files ending in *.stories.js
5+
const req = require.context('../stories', true, /.stories.js$/)
6+
function loadStories() {
7+
req.keys().forEach((filename) => req(filename))
8+
}
9+
10+
addDecorator(withKnobs)
11+
12+
configure(loadStories, module)

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ This package includes a script you can use to generate a JWT key. To use add you
1515

1616
follow the prompts. The generated key can then be used for your app. If you want to generate short keys you can refer to the script in keygen to get an idea of how to do this in node.
1717

18+
## Storybook
19+
20+
This project contains a [storybook](https://storybook.js.org) that shows examples of how the component can be used. To use this storybook follow these steps:
21+
22+
1. copy `devToken.js.rename` to `devToken.js`
23+
2. add a valid token from apple to `devToken.js`
24+
3. run `yarn` then `yarn storybook`
25+
4. visit the URL storybook gives you
26+
5. play with maps!
27+
1828
## MapKit Component
1929

2030
This is the component that will render a map. You'll need to provide either a `callbackUrl` or a `token` for this component to work.

devToken.js.rename

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'your token here.'

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
"test:coverage": "nwb test-react --coverage",
2222
"test:watch": "nwb test-react --server",
2323
"test": "nwb test-react",
24-
"keygen": "node keygen"
24+
"keygen": "node keygen",
25+
"storybook": "start-storybook -p 6006",
26+
"build-storybook": "build-storybook"
2527
},
2628
"lint-staged": {
2729
"*.{js,json,css,md}": [
@@ -38,6 +40,13 @@
3840
"little-loader": "^0.2.0"
3941
},
4042
"devDependencies": {
43+
"@storybook/addon-actions": "^3.4.6",
44+
"@storybook/addon-knobs": "^3.4.6",
45+
"@storybook/addon-links": "^3.4.6",
46+
"@storybook/addons": "^3.4.6",
47+
"@storybook/react": "^3.4.6",
48+
"babel-core": "^6.26.3",
49+
"babel-runtime": "^6.26.0",
4150
"colors": "^1.3.0",
4251
"flow-bin": "0.73.x",
4352
"gh-pages": "^1.2.0",

src/ErrorBoundry.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// @flow
2+
3+
import * as React from 'react'
4+
5+
type Props = {
6+
errorText: string,
7+
children: React.Node,
8+
}
9+
10+
type State = {
11+
hasError: boolean,
12+
}
13+
14+
export default class ErrorBoundary extends React.Component<Props, State> {
15+
state = { hasError: false }
16+
17+
componentDidCatch() {
18+
this.setState({ hasError: true })
19+
}
20+
21+
render() {
22+
if (this.state.hasError) {
23+
return <h1>{this.props.errorText}</h1>
24+
}
25+
return this.props.children
26+
}
27+
}

src/index.js

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,109 @@ import * as React from 'react'
44
import load from 'little-loader'
55
import invariant from 'invariant'
66

7+
import ErrorBoundry from './ErrorBoundry'
8+
79
type Props = {
8-
/**
9-
* Prop Description
10-
*/
1110
callbackUrl?: string,
1211
token?: string,
12+
tintColor?: string,
13+
showsUserLocationControl: boolean,
1314
}
1415

1516
type State = {
1617
mapKitIsReady: boolean,
1718
makKitIsStarted: boolean,
1819
}
1920

20-
export default class MapKit extends React.Component<Props, State> {
21+
const defaultPropsErrorText =
22+
"Either a `callbackUrl` or `token` is required for the `MapKit` component. One of these props must be set on init and can't be updated after the component is setup."
23+
24+
class MapKit extends React.Component<Props, State> {
2125
map = null
2226

27+
static defaultProps = {
28+
showsUserLocationControl: false,
29+
}
30+
2331
state = {
2432
mapKitIsReady: false,
2533
makKitIsStarted: false,
2634
}
2735

36+
checkProps = (props: Props) => {
37+
invariant(props.callbackUrl || props.token, defaultPropsErrorText)
38+
}
39+
40+
initMap = (props: Props) => {
41+
mapkit.init({
42+
authorizationCallback: (done) => {
43+
props.callbackUrl
44+
? fetch(props.callbackUrl)
45+
.then((res) => res.text())
46+
.then(done)
47+
: done(props.token)
48+
},
49+
})
50+
51+
this.map = new mapkit.Map('map')
52+
53+
this.setState({ mapKitIsReady: true })
54+
}
55+
2856
constructor(props: Props) {
2957
super(props)
3058

59+
this.checkProps(props)
60+
3161
load(
3262
'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js',
33-
function(err) {
34-
this.setState({ mapKitIsReady: true })
35-
},
63+
() => this.initMap(props),
3664
this,
3765
)
3866
}
3967

40-
static getDerivedStateFromProps(props: Props, state: State) {
41-
invariant(
42-
props.callbackUrl || props.token,
43-
'Either a `callbackUrl` or `token` is required for the `MapKit` component.',
44-
)
45-
}
68+
shouldComponentUpdate(nextProps: Props, nextState: State) {
69+
// make sure we have at least one init prop
70+
this.checkProps(nextProps)
4671

47-
componentDidUpdate() {
48-
if (this.state.mapKitIsReady && !this.state.makKitIsStarted) {
49-
mapkit.init({
50-
authorizationCallback: (done) => {
51-
if (this.props.callbackUrl) {
52-
fetch(this.props.callbackUrl)
53-
.then((res) => res.text())
54-
.then(done)
55-
} else {
56-
done(this.props.token)
57-
}
58-
},
59-
})
72+
let ComponentShouldUpdate = false
73+
74+
// if our init props have changed, re-init the map
75+
if (
76+
this.props.token !== nextProps.token ||
77+
this.props.callbackUrl !== nextProps.callbackUrl
78+
) {
79+
invariant(false, defaultPropsErrorText)
80+
}
6081

61-
this.map = new mapkit.Map('map')
82+
if (this.props.children !== nextProps.children) {
83+
ComponentShouldUpdate = true
84+
}
6285

63-
this.setState({ makKitIsStarted: true })
86+
if (this.state.mapKitIsReady) {
87+
// Update map based on props
88+
this.map.tintColor = nextProps.tintColor
89+
this.map.showsUserLocationControl = nextProps.showsUserLocationControl
6490
}
91+
92+
return ComponentShouldUpdate
6593
}
6694

6795
render() {
68-
const { callbackUrl, token, ...otherProps } = this.props
96+
const {
97+
callbackUrl,
98+
token,
99+
showsUserLocationControl,
100+
tintColor,
101+
...otherProps
102+
} = this.props
69103

70104
return <div id="map" {...otherProps} />
71105
}
72106
}
107+
108+
export default (props: Props) => (
109+
<ErrorBoundry errorText={defaultPropsErrorText}>
110+
<MapKit {...props} />
111+
</ErrorBoundry>
112+
)

stories/index.stories.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
3+
import { storiesOf } from '@storybook/react'
4+
import { text, boolean, number } from '@storybook/addon-knobs/react'
5+
6+
import devToken from '../devToken'
7+
import MapKit from '../src'
8+
9+
storiesOf('MapKit', module).add('with token', () => (
10+
<MapKit
11+
style={{ width: '100vw', height: '100vh' }}
12+
token={text('token', devToken)}
13+
tintColor={text('tintColor', undefined)}
14+
/>
15+
))

0 commit comments

Comments
 (0)