How does Redux change UI in React?
I think the confusion you have is that part of reducer composition and selectors.
Let's look at it in a reverse order, from the UI back.
In the connected component containers/VisibleTodoList.js it gets the todos
from the "state" (the global store object of redux
) inside mapStateToProps
, while passing it through the getVisibleTodos
method.
Which can be called a selector, as it selects and returns only a portion of the data that it receives:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
case 'SHOW_ALL':
default:
return todos
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
The state
(redux store) that passed to mapStateToProps
came from the root reducer reducers/index.js and is actually a single reducer (object) that represent the combination of all other reducers via the combineReducers
utility of redux
:
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
const todoApp = combineReducers({
todos,
visibilityFilter
})
export default todoApp
As you can see, the todos
reducer is there. so that's why inside the mapStateToProps
we call it like this state.todos
.
Here is the reducers/todos.js:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
export default todos
On each action of type 'ADD_TODO'
it will return a new state with the new todo
:
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
This the the action creator for it inside actions/index.js:
let nextTodoId = 0
export const addTodo = text => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
So here is the full flow of redux
(i omitted the button that calls the action as i assume this is obvious part for you).
Well, almost a full flow, none of this could have happened without the Provider
HOC that wraps the App
and inject the store to it in index.js:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now when the redux
state
changes, a call to mapStateToProps
is invoked that will return the new mapped props
. connect
will pass those new props
and this will trigger a new render
call (actually the entire react life cycle flow) to the connected component.
This way the UI will be re-rendered with the fresh new data from the store.