redux example WIP

This commit is contained in:
Dmitry Vasilev
2023-06-15 20:56:27 +03:00
parent 4996e8f86d
commit 42b3c9a292
14 changed files with 92 additions and 275 deletions

View File

@@ -4,18 +4,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Redux Todos Example</title>
<script src='https://unpkg.com/react/umd/react.development.js'></script>
<script src='https://unpkg.com/react-dom/umd/react-dom.development.js'></script>
<script src='https://unpkg.com/redux'></script>
<script src='https://unpkg.com/react-redux'></script>
<script type='module' src='../src/index.js'></script>
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` in this folder.
To create a production bundle, use `npm run build`.
-->
</body>
</html>

View File

@@ -1,25 +0,0 @@
import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('setVisibilityFilter should create SET_VISIBILITY_FILTER action', () => {
expect(actions.setVisibilityFilter('active')).toEqual({
type: 'SET_VISIBILITY_FILTER',
filter: 'active'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})

View File

@@ -1,14 +1,15 @@
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'
import Footer from './Footer.js'
import AddTodo from '../containers/AddTodo.js'
import VisibleTodoList from '../containers/VisibleTodoList.js'
const h = React.createElement
const App = () => (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
h('div', null,
h(AddTodo),
h(VisibleTodoList),
h(Footer),
)
)
export default App

View File

@@ -1,20 +1,15 @@
import React from 'react'
import FilterLink from '../containers/FilterLink'
import { VisibilityFilters } from '../actions'
import FilterLink from '../containers/FilterLink.js'
import { VisibilityFilters } from '../actions/index.js'
const h = React.createElement
const Footer = () => (
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
All
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
Active
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
Completed
</FilterLink>
</div>
h('div', null,
h('span', null, 'Show: '),
h(FilterLink, {filter: VisibilityFilters.SHOW_ALL}, 'All'),
h(FilterLink, {filter: VisibilityFilters.SHOW_ACTIVE}, 'Active'),
h(FilterLink, {filter: VisibilityFilters.SHOW_COMPLETED}, 'Completed'),
)
)
export default Footer

View File

@@ -1,22 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
const h = React.createElement
const Link = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
h('button', {
onClick,
disabled: active,
style:{
marginLeft: '4px',
}}
>
{children}
</button>
}
}, children)
)
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
export default Link

View File

@@ -1,21 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
const h = React.createElement
const Todo = ({ onClick, completed, text }) => (
<li
onClick={onClick}
style={{
h('li', {
onClick,
style: {
textDecoration: completed ? 'line-through' : 'none'
}}
>
{text}
</li>
},
}, text)
)
Todo.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}
export default Todo

View File

@@ -1,26 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'
import Todo from './Todo.js'
const h = React.createElement
const TodoList = ({ todos, toggleTodo }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => toggleTodo(todo.id)}
/>
)}
</ul>
h('ul', null,
todos.map(todo =>
h(Todo, {
key: todo.id,
...todo,
onClick: () => toggleTodo(todo.id),
})
)
)
)
TodoList.propTypes = {
todos: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired).isRequired,
toggleTodo: PropTypes.func.isRequired
}
export default TodoList

View File

@@ -1,27 +1,26 @@
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
import { addTodo } from '../actions/index.js'
const h = React.createElement
const AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
h('div', null,
h('form', { onSubmit: e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => input = node} />
<button type="submit">
Add Todo
</button>
</form>
</div>
},
h('input', {ref: node => {input = node}}),
h('button', {type: 'submit'}, 'Add Todo')
)
)
)
}
export default connect()(AddTodo)
export default ReactRedux.connect()(AddTodo)

View File

@@ -1,6 +1,5 @@
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
import { setVisibilityFilter } from '../actions/index.js'
import Link from '../components/Link.js'
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter
@@ -10,7 +9,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
export default connect(
export default ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Link)

View File

@@ -1,19 +1,13 @@
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { VisibilityFilters } from '../actions'
import { toggleTodo } from '../actions/index.js'
import TodoList from '../components/TodoList.js'
import { VisibilityFilters } from '../actions/index.js'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t => t.completed)
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
return {
[VisibilityFilters.SHOW_ALL]: todos,
[VisibilityFilters.SHOW_COMPLETED]: todos.filter(t => t.completed),
[VisibilityFilters.SHOW_ACTIVE]: todos.filter(t => !t.completed),
}[filter]
}
const mapStateToProps = state => ({
@@ -24,7 +18,7 @@ const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
})
export default connect(
export default ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

View File

@@ -1,15 +1,11 @@
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'
import App from './components/App.js'
import rootReducer from './reducers/index.js'
const store = createStore(rootReducer)
const h = React.createElement
render(
<Provider store={store}>
<App />
</Provider>,
const store = Redux.createStore(rootReducer)
ReactDOM.render(
h(ReactRedux.Provider, {store}, h(App)),
document.getElementById('root')
)

View File

@@ -1,8 +1,7 @@
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
import todos from './todos.js'
import visibilityFilter from './visibilityFilter.js'
export default combineReducers({
export default Redux.combineReducers({
todos,
visibilityFilter
})

View File

@@ -1,111 +0,0 @@
import todos from './todos'
describe('todos reducer', () => {
it('should handle initial state', () => {
expect(
todos(undefined, {})
).toEqual([])
})
it('should handle ADD_TODO', () => {
expect(
todos([], {
type: 'ADD_TODO',
text: 'Run the tests',
id: 0
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}
])
expect(
todos([
{
text: 'Run the tests',
completed: false,
id: 0
}
], {
type: 'ADD_TODO',
text: 'Use Redux',
id: 1
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}, {
text: 'Use Redux',
completed: false,
id: 1
}
])
expect(
todos([
{
text: 'Run the tests',
completed: false,
id: 0
}, {
text: 'Use Redux',
completed: false,
id: 1
}
], {
type: 'ADD_TODO',
text: 'Fix the tests',
id: 2
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}, {
text: 'Use Redux',
completed: false,
id: 1
}, {
text: 'Fix the tests',
completed: false,
id: 2
}
])
})
it('should handle TOGGLE_TODO', () => {
expect(
todos([
{
text: 'Run the tests',
completed: false,
id: 1
}, {
text: 'Use Redux',
completed: false,
id: 0
}
], {
type: 'TOGGLE_TODO',
id: 1
})
).toEqual([
{
text: 'Run the tests',
completed: true,
id: 1
}, {
text: 'Use Redux',
completed: false,
id: 0
}
])
})
})

View File

@@ -1,4 +1,4 @@
import { VisibilityFilters } from '../actions'
import { VisibilityFilters } from '../actions/index.js'
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
switch (action.type) {