Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Redux change UI in React?

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!

like image 428
Wordpressor Avatar asked Nov 25 '17 15:11

Wordpressor


1 Answers

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.

like image 170
Sagiv b.g Avatar answered Oct 14 '22 22:10

Sagiv b.g