A Pen by Mikhail Kuzmin on CodePen.
Created
January 11, 2017 18:51
-
-
Save darkleaf/c085b013a53d795a38447d070eada53e to your computer and use it in GitHub Desktop.
react-redux-dependency-injection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="todo-app"></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Components | |
function Layout({header, main, footer}) { | |
return ( | |
<section className="todoapp"> | |
{header} | |
{main} | |
</section> | |
) | |
} | |
Layout.propTypes = { | |
header: React.PropTypes.element.isRequired, | |
main: React.PropTypes.element.isRequired | |
} | |
class Header extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
text: props.text | |
} | |
this.handleChange = this.handleChange.bind(this) | |
this.handleSubmit = this.handleSubmit.bind(this) | |
} | |
handleChange(e) { | |
this.setState({text: e.target.value}) | |
} | |
handleSubmit(e) { | |
if (e.which === 13) { | |
const text = this.state.text | |
this.setState({text: ''}, | |
() => this.props.onSave(text)) | |
} | |
} | |
render() { | |
return ( | |
<header className="header"> | |
<h1>todos</h1> | |
<input | |
className="new-todo" | |
placeholder="What needs to be done?" | |
autofocus | |
value={this.state.text} | |
onChange={this.handleChange} | |
onKeyDown={this.handleSubmit} | |
/> | |
</header> | |
) | |
} | |
} | |
Header.propTypes = { | |
onSave: React.PropTypes.func.isRequired, | |
} | |
function Main({children}) { | |
return ( | |
<section className="main"> | |
<ul className="todo-list"> | |
{children} | |
</ul> | |
</section> | |
) | |
} | |
Main.propTypes = { | |
children: React.PropTypes.any.isRequired, | |
} | |
class Item extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
editing: false, | |
text: props.text | |
} | |
this.handleChange = this.handleChange.bind(this) | |
this.handleClick = this.handleClick.bind(this) | |
this.handleSubmit = this.handleSubmit.bind(this) | |
this.handleBlur = this.handleBlur.bind(this) | |
} | |
componentWillReceiveProps(nextProps) { | |
this.setState({text: nextProps.text}) | |
} | |
callOnChange() { | |
this.setState({editing: false}, | |
() => this.props.onChange(this.state.text)) | |
} | |
handleChange(e) { | |
this.setState({text: e.target.value}) | |
} | |
handleClick() { | |
this.setState({editing: true}, | |
() => this.input.focus()) | |
} | |
handleSubmit(e) { | |
if (e.which === 13) { | |
this.callOnChange() | |
} | |
} | |
handleBlur() { | |
this.callOnChange() | |
} | |
render() { | |
return ( | |
<li className={classNames({completed: this.props.completed, editing: this.state.editing})}> | |
<div className="view"> | |
<input | |
className="toggle" | |
type="checkbox" | |
onChange={this.props.onToggle} | |
checked={this.props.completed} | |
/> | |
<label onDoubleClick={this.handleClick}> | |
{this.state.text} | |
</label> | |
<button | |
className="destroy" | |
onClick={this.props.onDelete} | |
/> | |
</div> | |
<input | |
className="edit" | |
onBlur={this.handleBlur} | |
onKeyDown={this.handleSubmit} | |
onChange={this.handleChange} | |
ref={(input) => this.input = input} | |
value={this.state.text} /> | |
</li> | |
) | |
} | |
} | |
Item.propTypes = { | |
text: React.PropTypes.string.isRequired, | |
completed: React.PropTypes.bool.isRequired, | |
onChange: React.PropTypes.func.isRequired, | |
onToggle: React.PropTypes.func.isRequired, | |
onDelete: React.PropTypes.func.isRequired, | |
} | |
// Redux | |
const ADD_ITEM = 'ADD_ITEM' | |
const UPDATE_ITEM = 'UPDATE_ITEM' | |
const TOGGLE_ITEM = 'TOGGLE_ITEM' | |
const DELETE_ITEM = 'DELETE_ITEM' | |
function addItem(text) { | |
return { type: ADD_ITEM, text } | |
} | |
function toggleItem(id) { | |
return { type: TOGGLE_ITEM, id } | |
} | |
function updateItem(text) { | |
return { type: UPDATE_ITEM, text } | |
} | |
function deleteItem(id) { | |
return { type: DELETE_ITEM, id } | |
} | |
const initialState = { | |
idSeq: 0, | |
items: {} | |
} | |
function reducer(state = initialState, action) { | |
const newState = Object.assign({}, state) | |
switch (action.type) { | |
case ADD_ITEM: | |
newState.items[state.idSeq] = {text: action.text, completed: false} | |
newState.idSeq++ | |
return newState | |
case TOGGLE_ITEM: | |
newState.items[action.id].completed = !state.items[action.id].completed | |
return newState | |
case UPDATE_ITEM: | |
newState.items[action.id].text = action.text | |
return newState | |
case DELETE_ITEM: | |
delete newState.items[action.id] | |
return newState | |
default: | |
return state | |
} | |
} | |
// Containers | |
const ItemContainer = ReactRedux.connect( | |
(state, {id}) => { | |
const item = state.items[id] | |
return {text: item.text, | |
completed: item.completed} | |
}, | |
(dispatch, {id}) => { | |
return { | |
onChange: (text) => dispatch(updateItem(text)), | |
onToggle: () => dispatch(toggleItem(id)), | |
onDelete: () => dispatch(deleteItem(id)), | |
} | |
} | |
)(Item) | |
const HeaderContainer = ReactRedux.connect( | |
null, | |
dispatch => { | |
return { | |
onSave: (text) => dispatch(addItem(text)) | |
} | |
} | |
)(Header) | |
const MainContainer = ReactRedux.connect( | |
state => { | |
const ids = Object.keys(state.items) | |
return { | |
children: ids.map(id => <ItemContainer key={id} id={id} />) | |
} | |
} | |
)(Main) | |
class App extends React.Component { | |
render() { | |
return ( | |
<Layout | |
header={<HeaderContainer />} | |
main={<MainContainer />} | |
/> | |
) | |
} | |
} | |
let store = Redux.createStore(reducer) | |
store.dispatch(addItem('Learn about react')) | |
store.dispatch(addItem('Learn about redux')) | |
store.dispatch(addItem('Learn about dependency injection')) | |
ReactDOM.render( | |
<ReactRedux.Provider store={store}> | |
<App /> | |
</ReactRedux.Provider>, | |
document.getElementById("todo-app")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.2/react-redux.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://unpkg.com/[email protected]/index.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment