BAE BAE - 1 month ago 8
React JSX Question

redux todomvc: what happens when new one item?

I am learning redux todomvc, and have some questions about the source codes below. Any comments welcomed. Thanks.

Q1: why store.dispatch() and store.subscribe() not called? It seems that this example is a little different from the data flow introduction here.

Q2: can anyone explain what happens when new one item? How src/index.js, src/containers/App.js, src/components/Header.js, src/components/TodoTextInput.js work togother when new one item?

Q3: where are todos and actions from (src/containers/App.js)?

Q4: this state === store.getState() (in src/components/TodoTextInput.js)?

// src/index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import reducer from './reducers'
import 'todomvc-app-css/index.css'

const store = createStore(reducer)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

// src/containers/App.js
import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Header from '../components/Header'
import MainSection from '../components/MainSection'
import * as TodoActions from '../actions'

const App = ({todos, actions}) => (//Q3: where are todos and actions from?
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)

App.propTypes = {
todos: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}

const mapStateToProps = state => ({
todos: state.todos
})

const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(TodoActions, dispatch)
})

export default connect(
mapStateToProps,
mapDispatchToProps
)(App)


// src/components/Header.js
import React, { PropTypes, Component } from 'react'
import TodoTextInput from './TodoTextInput'

export default class Header extends Component {
static propTypes = {
addTodo: PropTypes.func.isRequired
}

handleSave = text => {
if (text.length !== 0) {
this.props.addTodo(text)
}
}

render() {
return (
<header className="header">
<h1>todos</h1>
<TodoTextInput newTodo// where is it from?
onSave={this.handleSave}
placeholder="What needs to be done?" />
</header>
)
}
}

// src/components/TodoTextInput.js
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'

export default class TodoTextInput extends Component {
static propTypes = {
onSave: PropTypes.func.isRequired,
text: PropTypes.string,
placeholder: PropTypes.string,
editing: PropTypes.bool,
newTodo: PropTypes.bool
}

state = {//Q4: this state === store.getState()?
text: this.props.text || ''
}

handleSubmit = e => {
const text = e.target.value.trim()
if (e.which === 13) {
this.props.onSave(text)
if (this.props.newTodo) {
this.setState({ text: '' })
}
}
}

handleChange = e => {
this.setState({ text: e.target.value })
}

handleBlur = e => {
if (!this.props.newTodo) {
this.props.onSave(e.target.value)
}
}

render() {
return (
<input className={
classnames({
edit: this.props.editing,
'new-todo': this.props.newTodo
})}
type="text"
placeholder={this.props.placeholder}
autoFocus="true"
value={this.state.text}
onBlur={this.handleBlur}
onChange={this.handleChange}
onKeyDown={this.handleSubmit} />
)
}
}

// src/components/TodoItem.js
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import TodoTextInput from './TodoTextInput'

export default class TodoItem extends Component {
static propTypes = {
todo: PropTypes.object.isRequired,
editTodo: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired,
completeTodo: PropTypes.func.isRequired
}

state = {
editing: false
}

handleDoubleClick = () => {
this.setState({ editing: true })
}

handleSave = (id, text) => {
if (text.length === 0) {
this.props.deleteTodo(id)
} else {
this.props.editTodo(id, text)
}
this.setState({ editing: false })
}

render() {
const { todo, completeTodo, deleteTodo } = this.props

let element
if (this.state.editing) {
element = (
<TodoTextInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
)
} else {
element = (
<div className="view">
<input className="toggle"
type="checkbox"
checked={todo.completed}
onChange={() => completeTodo(todo.id)} />
<label onDoubleClick={this.handleDoubleClick}>
{todo.text}
</label>
<button className="destroy"
onClick={() => deleteTodo(todo.id)} />
</div>
)
}

return (
<li className={classnames({
completed: todo.completed,
editing: this.state.editing
})}>
{element}
</li>
)
}
}

Answer

Q1 why store.dispatch() and store.subscribe() not called?

Because of 'container'. In redux, a container is a component that subscribes to changes in store. This is done with Redux's mapStateToProps ,mapDispatchToProps and finally the connect function calls inside the container file

The connect function does call store.subscribe internally.

Q2: can anyone explain what happens when new one item?

  1. the App container pass on the actions prop to the App component via mapDispatchToProps

  2. This prop actions contains the action addTodo and that is past down to the Header

  3. The Header component call the addTodo action upon the TextInput is saved

  4. The action addTodo is dispatched

  5. The reducer handles the action and update the state with new item. Store is updated.

  6. Store update triggers the App container to rerender with updated props because the App container has mapStateToProps

  7. done

Q3: where are todos and actions from (src/containers/App.js)?

Again this is because of the Redux's connect function. It will get the returned values from both mapStateToProps and mapDispatchToProps, merge them and pass it to the App component as props. todos comes from mapStateToProps and actions is from mapDispatchToProps

Q4 this state === store.getState()

Dont be confused. The state in the TodoTextInput is React's native component state and has nothing to do with Redux's state. However, if you need a state in your app, it is very common that one needs to decide if it should live in a Redux store or in the component itself.

If the state is only relevant to the component itself and no other component needs to know the status of that state, it indicates that it should live inside the component instead of being in Redux's store.

The state in the TodoTextInput component holds user input temporary before user commits the change. It is well fit to be an internal state of the component itself.