Skip to content

Commit 2906bb8

Browse files
committed
Initial commit
1 parent 05e9a92 commit 2906bb8

14 files changed

+356
-0
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin/*
2+
node_modules/*

.eslintrc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"parser" : "babel-eslint",
3+
"extends": "airbnb",
4+
"rules": {
5+
'react/jsx-filename-extension': [0],
6+
'react/no-danger': [0],
7+
}
8+
}

index.html

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<html>
2+
<head>
3+
<title>Testing Random Stuff</title>
4+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
5+
<link rel="stylesheet" type="text/css" href="/styles.css"/>
6+
</head>
7+
<body>
8+
<main id="root"></main>
9+
<script src="/bundle.js"></script>
10+
</body>
11+
</html>

package.json

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "react-redux-todo",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"dependencies": {
7+
"react": "^15.3.0",
8+
"react-dom": "^15.3.0",
9+
"react-redux": "^4.4.5",
10+
"redux": "^3.5.2",
11+
"redux-devtools": "^3.3.1"
12+
},
13+
"devDependencies": {
14+
"autoprefixer": "^6.4.0",
15+
"babel-core": "^6.13.2",
16+
"babel-loader": "^6.2.4",
17+
"babel-preset-es2015": "^6.13.2",
18+
"babel-preset-react": "^6.11.1",
19+
"babel-preset-stage-0": "^6.5.0",
20+
"css-loader": "^0.23.1",
21+
"eslint": "^3.3.1",
22+
"eslint-config-airbnb": "^10.0.1",
23+
"eslint-plugin-import": "^1.13.0",
24+
"eslint-plugin-jsx-a11y": "^2.1.0",
25+
"eslint-plugin-react": "^6.1.1",
26+
"extract-text-webpack-plugin": "^1.0.1",
27+
"file-loader": "^0.9.0",
28+
"node-sass": "^3.8.0",
29+
"postcss-loader": "^0.10.0",
30+
"sass-loader": "^4.0.0",
31+
"style-loader": "^0.13.1",
32+
"webpack": "^1.13.1",
33+
"webpack-dev-server": "^1.14.1"
34+
},
35+
"scripts": {
36+
"dev": "webpack-dev-server --port 3000",
37+
"test": "echo \"Error: no test specified\" && exit 1"
38+
},
39+
"repository": {
40+
"type": "git",
41+
"url": "git+https://github.com/bishwei/react-redux-todo.git"
42+
},
43+
"author": "",
44+
"license": "ISC",
45+
"bugs": {
46+
"url": "https://github.com/bishwei/react-redux-todo/issues"
47+
},
48+
"homepage": "https://github.com/bishwei/react-redux-todo#readme"
49+
}

src/actions.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const ADD_TODO = 'ADD_TODO';
2+
export const TOGGLE_TODO = 'TOGGLE_TODO';
3+
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
4+
5+
export const VisibilityFilters = {
6+
SHOW_ALL: 'SHOW_ALL',
7+
SHOW_COMPLETED: 'SHOW_COMPLETED',
8+
SHOW_ACTIVE: 'SHOW_ACTIVE',
9+
};
10+
11+
export function addTodo(text) {
12+
return { type: ADD_TODO, text };
13+
}
14+
15+
export function toggleTodo(index) {
16+
return { type: TOGGLE_TODO, index };
17+
}
18+
19+
export function setVisibilityFilter(filter) {
20+
return { type: SET_VISIBILITY_FILTER, filter };
21+
}

src/components/link.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { PropTypes } from 'react';
2+
3+
const onLinkClick = (onClick) => (event) => {
4+
event.preventDefault();
5+
onClick();
6+
};
7+
8+
const Link = ({ active, children, onClick }) => {
9+
if (active) {
10+
return <span>{children}</span>;
11+
}
12+
13+
return (
14+
<button onClick={onLinkClick(onClick)}>
15+
{children}
16+
</button>
17+
);
18+
};
19+
20+
Link.propTypes = {
21+
active: PropTypes.bool.isRequired,
22+
children: PropTypes.node.isRequired,
23+
onClick: PropTypes.func.isRequired,
24+
};
25+
26+
export default Link;

src/components/todo-list.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { PropTypes } from 'react';
2+
import Todo from './todo';
3+
4+
const onClick = (onTodoClick, id) => () => {
5+
onTodoClick(id);
6+
};
7+
8+
const TodoList = ({ todos, onTodoClick }) =>
9+
<ul>
10+
{todos.map(({ completed, id, text }) =>
11+
<Todo
12+
completed={completed}
13+
key={id}
14+
text={text}
15+
onClick={onClick(onTodoClick, id)}
16+
/>
17+
)}
18+
</ul>;
19+
20+
TodoList.displayName = 'TodoList';
21+
22+
TodoList.propTypes = {
23+
todos: PropTypes.arrayOf(PropTypes.shape({
24+
completed: PropTypes.bool.isRequired,
25+
id: PropTypes.number.isRequired,
26+
text: PropTypes.string.isRequired,
27+
}).isRequired).isRequired,
28+
onTodoClick: PropTypes.func.isRequired,
29+
};
30+
31+
export default TodoList;

src/components/todo.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { PropTypes } from 'react';
2+
3+
const Todo = ({ completed, onClick, text }) =>
4+
<li
5+
onClick={onClick}
6+
style={{
7+
textDecoration: completed ? 'line-through' : 'none',
8+
}}
9+
>
10+
{ text }
11+
</li>;
12+
13+
Todo.displayName = 'Todo';
14+
Todo.propTypes = {
15+
completed: PropTypes.bool.isRequired,
16+
onClick: PropTypes.func.isRequired,
17+
text: PropTypes.string.isRequired,
18+
};
19+
20+
export default Todo;

src/main.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { render } from 'react-dom';
3+
import { Provider } from 'react-redux';
4+
import { createStore } from 'redux';
5+
import todoApp from './reducers';
6+
import AddTodo from './parent-components/add-todo';
7+
import VisibleTodoList from './parent-components/visible-todo-list';
8+
9+
const App = () =>
10+
<div>
11+
<AddTodo />
12+
<VisibleTodoList />
13+
</div>;
14+
15+
let store = createStore(todoApp);
16+
17+
render(
18+
<Provider store={store}>
19+
<App />
20+
</Provider>,
21+
document.getElementById('root')
22+
);

src/parent-components/add-todo.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import { connect } from 'react-redux';
3+
import { addTodo } from '../actions';
4+
5+
const AddTodo = ({ dispatch }) => {
6+
let input;
7+
8+
const onSubmit = () => (event) => {
9+
const { value } = input;
10+
event.preventDefault();
11+
12+
if (!value.trim()) {
13+
return;
14+
}
15+
16+
dispatch(addTodo(value));
17+
input.value = '';
18+
};
19+
20+
return (
21+
<div>
22+
<form onSubmit={onSubmit()}>
23+
<input ref={ref => { input = ref; }} />
24+
<button type="submit">
25+
Add Todo
26+
</button>
27+
</form>
28+
</div>
29+
);
30+
};
31+
32+
export default connect()(AddTodo);
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { connect } from 'react-redux';
2+
import { toggleTodo } from '../actions';
3+
import TodoList from '../components/todo-list';
4+
5+
const getVisibleTodos = (todos, filter) => {
6+
switch (filter) {
7+
case 'SHOW_ALL':
8+
return todos;
9+
case 'SHOW_COMPLETED':
10+
return todos.filter(t => t.completed);
11+
case 'SHOW_ACTIVE':
12+
return todos.filter(t => !t.completed);
13+
}
14+
};
15+
16+
const mapStateToProps = (state) => {
17+
console.log(state);
18+
const { todos, visibilityFilter } = state;
19+
return {
20+
todos: getVisibleTodos(todos, visibilityFilter),
21+
};
22+
};
23+
24+
const mapDispatchToProps = (dispatch) => (
25+
{
26+
onTodoClick: (id) => {
27+
dispatch(toggleTodo(id));
28+
},
29+
}
30+
);
31+
32+
const VisibleTodoList = connect(
33+
mapStateToProps,
34+
mapDispatchToProps
35+
)(TodoList);
36+
37+
export default VisibleTodoList;

src/reducers.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
ADD_TODO,
3+
TOGGLE_TODO,
4+
SET_VISIBILITY_FILTER,
5+
VisibilityFilters,
6+
} from './actions';
7+
8+
9+
function todoReducer(state = [], action) {
10+
switch (action.type) {
11+
case ADD_TODO:
12+
return [
13+
...state,
14+
{
15+
text: action.text,
16+
completed: false,
17+
},
18+
];
19+
case TOGGLE_TODO:
20+
return state.map((todo, index) => {
21+
if (index === action.index) {
22+
return {
23+
...todo,
24+
completed: !todo.completed,
25+
};
26+
}
27+
return todo;
28+
});
29+
default:
30+
return state;
31+
}
32+
}
33+
34+
function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) {
35+
switch (action.type) {
36+
case SET_VISIBILITY_FILTER:
37+
return action.filter;
38+
default:
39+
return state;
40+
}
41+
}
42+
43+
export default function todoApp(state = {}, action) {
44+
return {
45+
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
46+
todos: todoReducer(state.todos, action),
47+
};
48+
}

src/store.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createStore } from 'redux';
2+
import todoApp from './reducers';
3+
4+
let store = createStore(todoApp);

webpack.config.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const autoprefixer = require('autoprefixer');
2+
const DefinePlugin = require('webpack').DefinePlugin;
3+
const ExtractTextPlugin = require('extract-text-webpack-plugin');
4+
const path = require('path');
5+
6+
const plugins = [
7+
new ExtractTextPlugin('styles.css'),
8+
];
9+
10+
module.exports = {
11+
devtool: 'cheap-module-eval-source-map',
12+
module: {
13+
loaders: [
14+
{
15+
test: /\.js$/,
16+
exclude: /(node_modules)/,
17+
loader: 'babel',
18+
query: {
19+
presets: ['es2015', 'stage-0', 'react']
20+
}
21+
},
22+
{
23+
test: /\.scss$/,
24+
loader: ExtractTextPlugin.extract('css!sass!postcss')
25+
},
26+
{
27+
test: /\.(gif|png|jpg)$/, loader: 'url-loader?limit=8192&name=images/[name].[ext]'
28+
},
29+
{
30+
test: /\.html$/,
31+
loader: 'file?name=[name].[ext]',
32+
}
33+
]
34+
},
35+
plugins: plugins,
36+
postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ],
37+
entry: [
38+
'./src/main.js',
39+
'./index.html',
40+
],
41+
output: {
42+
filename: 'bundle.js',
43+
publicPath: '/'
44+
},
45+
};

0 commit comments

Comments
 (0)