1
/
5

Redux-driven UI flow

React and Vue are libraries for building module user interfaces. However, alone they do not provide a full solution for managing the state of a user interface. Queue the flux architecture. in these two cases, the most common libraries are Redux and Vuex.

After several iterations, I found a pattern I’m happy with for managing UI. I’ve used both Vue and React heavily, and will use React + Redux for the example, but the ideas apply to any framework.

The situation

I’m building a complex, SPA to show the current status of the various services my company offers (we call them areas). For example, Payroll, HR, and Tax. A menu will show all the areas, and when a user clicks on one, they are shown extra details about said area, such as the number of expenses this month, and so on.

I’m going to all payroll, HR and Accounting I’m going to call <Area> and the right hand side <AreaDetails>. When you click an area, the area details are shown. If no area is selected (the defaut), AreaDetails is not rendered.

The goal is to have a nice separation of the ui state (what is displayed) and the actual data (the areas and the details).

There are a number of ways to go about this. Firstly, let’s take a look at how we can model the state:

state = {
  areas: {
    '0': { name: 'payroll', isSelected: true },
    '1': { name: 'hr', isSelected: false },
    '2': { name: 'accounting', isSelected: true }
  }
}

Sure, this works. But we are mixing concerns: the data (name, details…) and isSelected, and the interface.

How about:

state = {
  areas: {
    '0': { name: 'payroll' },
    '1': { name: 'hr' },
    '2': { name: 'accounting' }
  },
  ui: {
    selectedArea: 2  
  }
}

Much nicer. The data no longer is concerned with whether is it visible.

Now the actions might looks something like:

// actions/areas.js. Fetch from some API.  It is async and will require something like redux-thunk.const getAreas = () => {
  return {
    type: 'GET_AREAS_ASYNC'
  }
} 
// actions/ui.js. Dispatch an action to toggle selectedArea.
const setSelectedArea = (id) => {
  return {
    type: 'SET_SELECTED_AREA'
    payload: {
      id
    }
  }
}

And the reducers:

// reducers/areas.js
export const areas = (state = {}, action) => {
  switch (action.type) {
    case GET_AREAS_ASYNC.SUCCESS: {
       // assuming the API gives up the follow structure:
       // { '0': { name: 'payroll }, { '1': 'name' ... } }
      return state = action.payload 
    default:
      return state
  }
}
// reducers/ui.js
export const ui = (state = { selectedArea: null }, action) => {
  switch (action.type) {
    case AREA_SELECTED:
      return {...state, selectedArea: action.payload.id}
    default:
      return state
  }
}

Finally, the rendering of the app, driven by the ui.js reducer and state:

render() {
    const areaDetails = this.props.selectedArea !== null 
      ? <AreaContainerDetails area ={this.props.areas[this.props.selectedArea]}/> 
      : null
return(
      <div>
        <div>
          { 
            Object.entries(this.props.areas).map(([k, v]) => {
              return <AreaContainer key={k} area={v} />
            })
          }
        </div>
        {areaDetails} // if no area selected, don't render
      </div>
    )
  }
}
function mapStateToProps(state) { 
  console.log(state)
  return {
    areas: state.areas,
    selectedArea: state.ui.selectedArea
  }
}

Notice the first line of render() decides what to show, based on the ui state. The area areas do not have knowledge of whether or not they are selected — good separation of concerns, and we can easily build a more complex UI using this technique.

I’m interested in how other people are managing their UI using redux or vuex — I’ll probably put something up about how this pattern can be applied in Vue soon, too. Let me know if you have any thoughts or comments, or a better way of doing it!