I've been trying hard to wrap my head around this concept but with no luck. The official React tutorial is really good but for me it's way too complex and just simply a little bit too hard.
I'm trying to understand Redux and so far I can create actions, reducers, I can dispatch an action and then see how the store state changes after dispatching it. I also managed to understand connect
of react-redux and it works perfectly well and I'm able to trigger dispatches from any place in my app. So I think I almost got it figured out. Almost, because here's the elephant in the room - I dispatch the action, I see the Redux state change but HOW DO I CHANGE THE UI?
For example I have text
object in my initial state with value Initial text
and once a button is clicked I want to change the text to Clicked text
and DISPLAY the text somewhere in the UI (let's say on the button).
How do I "access" the Redux state in React and how do I dynamicaly change it?
It seems to be very simple without React, e.g..: https://jsfiddle.net/loktar/v1kvcjbu/ - render
function handles everything, I understand everything that happens here.
But on the other side "todo" from official React+Redux tutorial looks like this: https://redux.js.org/docs/basics/ExampleTodoList.html , it's so sophisticated I have no idea where to look.
The Add Todo
button submits a form that dispatches dispatch(addTodo(input.value))
action. The action itself does nothing just increases the ID and passes the text to the store and the reducer just returns the new state. Then how the todo is being rendered on the page? Where? I'm lost at this point. Maybe there are simpler tutorials, I'd love to have an one-button Redux tutorial it still can be complicated with multiple layers of components :(
I suspect the magic happens in TodoList.js as they're mapping over something there but still I have no idea where todos come from there, and what it has to do with Redux (there's no simple reducer/action/dispatch in that file).
Thanks for any help!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With