Skip to content

Commit c75b9f2

Browse files
committed
In the beginning...
0 parents  commit c75b9f2

File tree

17 files changed

+486
-0
lines changed

17 files changed

+486
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist
2+
node_modules
3+
typings
4+
*.swp
5+
npm-debug.log
6+
src/**/*.js

CONTRIBUTING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Contributing
2+
===============================================================================
3+
4+
Have something to add? Feature requests, bug reports, and contributions are
5+
enormously welcome!
6+
7+
1. Fork this repo
8+
2. Update the tests and implement the change
9+
3. Submit a [pull request][github-pull-request]
10+
11+
(hint: following the conventions in the [the code review
12+
checklist][code-review-checklist] will expedite review and merge)
13+
14+
[github-pull-request]: help.github.com/pull-requests/
15+
[code-review-checklist]: https://github.com/rjz/code-review-checklist
16+

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// MIT License
2+
3+
Copyright (C) RJ Zaworski <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
the Software, and to permit persons to whom the Software is furnished to do so,
10+
subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Typescript + React + Redux
2+
3+
A dead-simple counter to demonstrate structure of a TypeScript-enabled redux
4+
application.
5+
6+
Install and build demo:
7+
8+
$ npm install
9+
$ npm run build
10+
11+
...now open "index.html" in your browser to see the counter in action!

index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Counter Demo</title>
5+
</head>
6+
<body>
7+
<div id="redux-app-root">
8+
Try building the demo:
9+
<pre>
10+
</pre>
11+
...and refreshing this page!
12+
</div>
13+
<script src="node_modules/react/dist/react.js"></script>
14+
<script src="node_modules/react-dom/dist/react-dom.js"></script>
15+
<script src="dist/bundle.js"></script>
16+
</body>
17+
</html>

karma.conf.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const webpack = require('webpack');
2+
3+
const webpackConfig = Object.assign({}, require('./webpack.config'), {
4+
entry: {},
5+
output: {},
6+
cache: true,
7+
externals: {}
8+
});
9+
10+
module.exports = function(config) {
11+
config.set({
12+
basePath: '',
13+
frameworks: ['jasmine', 'sinon'],
14+
files: [
15+
'src/**/__tests__/*spec.ts',
16+
'src/**/__tests__/*spec.tsx'
17+
],
18+
preprocessors: {
19+
'src/**/*spec.{ts,tsx}': ['webpack']
20+
},
21+
webpack: webpackConfig,
22+
webpackMiddleware: {
23+
noInfo: true
24+
},
25+
reporters: ['spec'],
26+
port: 9876,
27+
colors: true,
28+
logLevel: config.LOG_INFO,
29+
autoWatch: true,
30+
browsers: ['PhantomJS'],
31+
singleRun: false
32+
})
33+
};

package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "typescript-react-redux",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"setup": "typings install",
9+
"build": "npm run setup && npm run test && webpack",
10+
"watch": "webpack --watch",
11+
"lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'",
12+
"pretest": "npm run lint",
13+
"test": "karma start karma.conf --single-run=true"
14+
},
15+
"typings": "typings/index.d.ts",
16+
"author": "RJ Zaworski <[email protected]> (http://rjzaworski.com)",
17+
"license": "ISC",
18+
"dependencies": {
19+
"react": "^15.3.0",
20+
"react-dom": "^15.3.0",
21+
"react-redux": "^4.4.5",
22+
"redux": "^3.5.2"
23+
},
24+
"devDependencies": {
25+
"jasmine-core": "2.4.1",
26+
"karma": "^1.1.2",
27+
"karma-jasmine": "1.0.2",
28+
"karma-phantomjs-launcher": "^1.0.1",
29+
"karma-sinon": "1.0.5",
30+
"karma-spec-reporter": "0.0.26",
31+
"karma-webpack": "^1.7.0",
32+
"phantomjs-prebuilt": "^2.1.10",
33+
"react-addons-test-utils": "^15.3.0",
34+
"sinon": "1.17.4",
35+
"source-map-loader": "0.1.5",
36+
"ts-loader": "^0.8.2",
37+
"tslint": "^3.14.0",
38+
"typescript": "^1.8.10",
39+
"typings": "1.3.2",
40+
"webpack": "^1.13.1"
41+
}
42+
}

src/actions/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export interface Action<T, P> {
2+
type: T
3+
payload?: P
4+
}
5+
6+
type CounterActionTypes = 'INCREMENT_COUNTER' | 'DECREMENT_COUNTER'
7+
8+
type IncrementCounterPayload = { delta: number }
9+
10+
export type IncrementCounter = Action<CounterActionTypes, IncrementCounterPayload>
11+
12+
export const incrementCounter = (delta: number): IncrementCounter => ({
13+
type: 'INCREMENT_COUNTER',
14+
payload: { delta },
15+
})
16+
17+
type DecrementCounterPayload = { delta: number }
18+
19+
export type DecrementCounter = Action<CounterActionTypes, DecrementCounterPayload>
20+
21+
export const decrementCounter = (delta: number): DecrementCounter => ({
22+
type: 'INCREMENT_COUNTER',
23+
payload: { delta },
24+
})
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as React from 'react'
2+
import * as TestUtils from 'react-addons-test-utils'
3+
4+
import { createStore } from 'redux'
5+
import { Provider } from 'react-redux'
6+
7+
import { Counter } from '../counter'
8+
import { reducers } from '../../reducers'
9+
10+
describe('components/Counter', () => {
11+
12+
function renderElement (el: React.ReactElement<{}>) {
13+
return TestUtils.renderIntoDocument(el) as React.Component<{}, {}>
14+
}
15+
16+
function findComponentByType(root: React.Component<{}, {}>, type: any): React.Component<{}, {}> {
17+
return TestUtils.findRenderedComponentWithType(root, type)
18+
}
19+
20+
function setup (): React.Component<{}, {}> {
21+
const store = createStore(reducers)
22+
const wrapper = renderElement(
23+
<Provider store={store}>
24+
<Counter label='a counter!' />
25+
</Provider>)
26+
const counter = findComponentByType(wrapper, Counter)
27+
return counter
28+
}
29+
30+
it('starts at 0', () => {
31+
const counter = setup()
32+
const pre = TestUtils.findRenderedDOMComponentWithTag(counter, 'pre')
33+
expect(pre.textContent).toEqual('counter = 0')
34+
})
35+
36+
it('shows a label', () => {
37+
const counter = setup()
38+
const label = TestUtils.findRenderedDOMComponentWithTag(counter, 'label')
39+
expect(label.textContent).toEqual('a counter!')
40+
})
41+
42+
43+
describe('clicking "increment"', () => {
44+
let counter: React.Component<{}, {}>
45+
46+
beforeEach(() => {
47+
counter = setup()
48+
const buttonEl = TestUtils.findRenderedDOMComponentWithTag(counter, 'button')
49+
TestUtils.Simulate.click(buttonEl)
50+
TestUtils.Simulate.click(buttonEl)
51+
TestUtils.Simulate.click(buttonEl)
52+
})
53+
54+
it('increments counter', () => {
55+
const pre = TestUtils.findRenderedDOMComponentWithTag(counter, 'pre')
56+
expect(pre.textContent).toEqual('counter = 3')
57+
})
58+
})
59+
})

src/components/counter.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as React from 'react'
2+
import * as redux from 'redux'
3+
import { connect } from 'react-redux'
4+
5+
import { incrementCounter } from '../actions'
6+
import { Store } from '../reducers'
7+
8+
interface OwnProps {
9+
label: string
10+
}
11+
12+
interface ConnectedState {
13+
counter: { value: number }
14+
}
15+
16+
interface ConnectedDispatch {
17+
increment: (n: number) => void
18+
}
19+
20+
const mapStateToProps = (state: Store.All, ownProps: OwnProps): ConnectedState => ({
21+
counter: state.counter,
22+
})
23+
24+
const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): ConnectedDispatch => ({
25+
increment: (n: number) => {
26+
dispatch(incrementCounter(n))
27+
},
28+
})
29+
30+
class CounterComponent extends React.Component<ConnectedState & ConnectedDispatch & OwnProps, {}> {
31+
32+
_onClickIncrement = () => {
33+
this.props.increment(1)
34+
}
35+
36+
render () {
37+
const { counter, label } = this.props
38+
return <div>
39+
<label>{label}</label>
40+
<pre>counter = {counter.value}</pre>
41+
<button ref='increment' onClick={this._onClickIncrement}>click me!</button>
42+
</div>
43+
}
44+
}
45+
46+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787
47+
export const Counter: React.ComponentClass<OwnProps> = connect(mapStateToProps, mapDispatchToProps)(CounterComponent)

src/index.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react' // tslint:disable-line
2+
import * as ReactDOM from 'react-dom'
3+
import * as Redux from 'redux'
4+
import { Provider } from 'react-redux'
5+
6+
import {
7+
reducers,
8+
Store,
9+
} from './reducers'
10+
11+
import { Counter } from './components/counter'
12+
13+
let store: Redux.Store<Store.All> = Redux.createStore(reducers)
14+
15+
// Commented out ("let HTML app be HTML app!")
16+
window.addEventListener('DOMContentLoaded', () => {
17+
const rootEl = document.getElementById('redux-app-root')
18+
if (rootEl) ReactDOM.render(
19+
<Provider store={store}>
20+
<Counter label='who\'s counting?' />
21+
</Provider>
22+
, rootEl)
23+
})

src/reducers/__tests__/index_spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createStore } from 'redux'
2+
3+
import { reducers } from '../index'
4+
import { incrementCounter } from '../../actions'
5+
6+
describe('reducers/counter', () => {
7+
it('starts at 0', () => {
8+
const store = createStore(reducers)
9+
const { counter } = store.getState()
10+
expect(counter.value).toEqual(0)
11+
})
12+
13+
it('increments', (done) => {
14+
const store = createStore(reducers)
15+
store.subscribe(() => {
16+
const { counter } = store.getState()
17+
expect(counter.value).toEqual(3)
18+
done()
19+
})
20+
store.dispatch(incrementCounter(3))
21+
})
22+
})

src/reducers/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { combineReducers } from 'redux'
2+
3+
import {
4+
IncrementCounter,
5+
DecrementCounter,
6+
} from '../actions'
7+
8+
export namespace Store {
9+
10+
export type Counter = { value: number }
11+
12+
export type All = {
13+
counter: Counter
14+
}
15+
}
16+
17+
const initialState: Store.Counter = {
18+
value: 0,
19+
}
20+
21+
function counter (state: Store.Counter = initialState, action: IncrementCounter | DecrementCounter): Store.Counter {
22+
const { value } = state
23+
switch (action.type) {
24+
case 'INCREMENT_COUNTER': // not checked, pending Microsoft/TypeScript#9407
25+
const { payload } = action as IncrementCounter
26+
const newValue = value + payload.delta
27+
return { value: newValue }
28+
}
29+
30+
return state
31+
}
32+
33+
export const reducers = combineReducers<Store.All>({
34+
counter,
35+
})

tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"outDir": "./dist/",
4+
"sourceMap": true,
5+
"noImplicitAny": true,
6+
"module": "commonjs",
7+
"target": "ES5",
8+
"jsx": "react"
9+
},
10+
"files": [
11+
"typings/index.d.ts"
12+
],
13+
"exclude": []
14+
}

0 commit comments

Comments
 (0)