Skip to content

Instantly share code, notes, and snippets.

@darkleaf
Created January 11, 2017 18:51
Show Gist options
  • Save darkleaf/c085b013a53d795a38447d070eada53e to your computer and use it in GitHub Desktop.
Save darkleaf/c085b013a53d795a38447d070eada53e to your computer and use it in GitHub Desktop.
react-redux-dependency-injection
<div id="todo-app"></div>
// 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"))
<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>
<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