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 charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Redux Todos Example</title> <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> </head>
<body> <body>
<div id="root"></div> <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> </body>
</html> </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.js'
import Footer from './Footer' import AddTodo from '../containers/AddTodo.js'
import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList.js'
import VisibleTodoList from '../containers/VisibleTodoList'
const h = React.createElement
const App = () => ( const App = () => (
<div> h('div', null,
<AddTodo /> h(AddTodo),
<VisibleTodoList /> h(VisibleTodoList),
<Footer /> h(Footer),
</div> )
) )
export default App export default App

View File

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

View File

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

View File

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

View File

@@ -1,26 +1,17 @@
import React from 'react' import Todo from './Todo.js'
import PropTypes from 'prop-types'
import Todo from './Todo' const h = React.createElement
const TodoList = ({ todos, toggleTodo }) => ( const TodoList = ({ todos, toggleTodo }) => (
<ul> h('ul', null,
{todos.map(todo => todos.map(todo =>
<Todo h(Todo, {
key={todo.id} key: todo.id,
{...todo} ...todo,
onClick={() => toggleTodo(todo.id)} onClick: () => toggleTodo(todo.id),
/> })
)} )
</ul> )
) )
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 export default TodoList

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
import { combineReducers } from 'redux' import todos from './todos.js'
import todos from './todos' import visibilityFilter from './visibilityFilter.js'
import visibilityFilter from './visibilityFilter'
export default combineReducers({ export default Redux.combineReducers({
todos, todos,
visibilityFilter 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) => { const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
switch (action.type) { switch (action.type) {