Introduction

When I first started building apps with React & Redux, I didn’t know how to properly manage UI state. By UI state I mean things like the opening and closing of modals, left/right nav bar toggling, and other UI elements that apply to the entire shell of an application.

Questions regarding how to handle UI state are asked continuously on development boards, so I’d like to share with you how I maintain it. It’s a best practice that has improved the management of my applications.

In a Nutshell

To get right to the point, a best practice is to use a special UI Reducer to manage the UI state of your application. If you’re familiar with Redux, you’re aware that you create separate reducers for different categories of data that flow through your app.

For example, data in your application that refers to a user (user id, display name, photo, email, is/not signed in, etc.) would be maintained in a special User Reducer.

If your application was doing something specific like recording audio, then you’d probably establish an Audio Reducer that would maintain all audio related information (is/not recording, audio count, audio list, current id of audio playing, etc.).

Since the UI Reducer is responsible for managing the UI state of an app, relevant information that pertains to the overall UI should be maintained there. Using this pattern lets sibling components communicate with each other when an action in one should trigger a reaction or visual change in another.

I’m going to demonstrate how I use a special UI Reducer in code. But before I do that, we should clarify the different types of state that typically exist within an app (it’s a confusing topic for beginners). This will help us clearly define what we mean by the term “UI state”.

The Two Main Types of State

Modern front-end applications that are developed by using encapsulated components have two main types of state: application state, and internal component state.

Application State

Application state refers to the data that flows through your app (the information your user consumes), as well as the state that is relevant to more than one component. Sometimes it’s referred to as global state.

Application state is subdivided into multiple categories of data. These multiple categories, or stores, roughly correlate to what we previously referred to as “models”. Each category is maintained by its own special reducer. When using Redux, these reducers ultimately get combined into one object that is accessible throughout an app.

Let’s look at an example. Note the piece of data in the snapshot below. It corresponds to an audio recording in my app Voice Record Pro (I had a lot of fun developing this).

www.voicerecorpro.com — Single Page Application and Progressive Web App

That same piece of data, data pertaining to an audio recording, is used in a different component as well.

So in this example all audio recording data, managed by a specific Audio Reducer, is accessible to several different components in the app. This is an example of Application State. The state, or data, that’s universally accessible to any component throughout an application.

Internal Component State

When you develop an interface by thinking and developing in components (I like using React), each encapsulated component has access to its own internal state. This internal state isn’t accessible anywhere else except for the component itself.

For example, below we have a snapshot of a right-hand drawer I call the <Drawer /> that includes the controls for a given audio recording. When the <Drawer /> is first displayed, the audio recording is not playing.

DetailsView — hit the play button

When the play button is pressed in the <Drawer /> component, the audio recording gets played. As a result, the UI for this given component changes to indicate that the recording is being played, and the play button turns into a pause button.

The state of this encapsulated component (isPlaying: true/false) is only accessible within this component and is used to control the UI. It’s not accessible anywhere else.

That’s what we refer to as internal component state.

UI State (A Subset of Application State)

Now that we understand the two different main types of state that exist within an application, let me now illustrate a subset of application state: UI state.

In the snapshot below, a user can click on a hamburger icon that is displayed within a <Header /> component.

Once clicked or tapped, the <Header /> component calls an action that says “the <LeftNav /> component should now be open”.

Because the <LeftNav /> component is listening to state changes relevant to the UI state via Redux, it now toggles open.

This is an example of UI state that could easily be managed by a special UI Reducer. It’s also an example of how to trigger changes between sibling components. It’s also an example of how to pass data amongst components that don’t have a parent/child relationship and hence cannot pass information via props (if you don’t understand what I mean by that yet, that’s OK. It’s React-specific parlance).

The UI Reducer In Action

So what does the code for a UI Reducer actually look like? Let’s look at another example of UI state in action: the toggling of a universal modal component.

In my <Drawer /> component, there’s a delete icon. This feature allows a user to delete an audio recording.

When a user clicks or taps the delete icon, a modal is displayed confirming if the user really wants to delete this audio recording. This confirmation request is displayed to the user via a reusable <Modal /> component which is accessible anywhere in the app.

This <Modal /> component is embedded within the main shell of this application, and its display is controlled by a state key in the UI Reducer called “showModal”.

Click to expand

The UI state key “showModal” is initially set to “false” because we don’t want it to be displayed by default. We’re going to toggle this when we want to display the modal to a user.

Toggling UI State To Display the <Modal />

To display the<Modal /> component, the code does the following.

Step 1, an action creator gets called when a user clicks or taps the delete icon. This action creator is called showModal().

containers/Drawer/index.js ... deleteAudio=() => {

actions.ui.showModal({

title : 'Delete recording?',

actions : modalActions

});

} ...

Step 2, an action gets dispatched to indicate that the universal <Modal /> should be opened. In other words, we’re telling the application that the UI state needs to change so that the modal gets displayed.

core/actions/actions-ui.js ... /**

* showModal - Open the modal

*/

export function showModal(obj) {

return {

type : constants.SHOW_MODAL,

title : obj.title,

modalActions: obj.actions

};

} ...

Step 3, our special UI Reducer that’s listening to this specific action does its thing and returns the new state of the application.

core/reducers/reducer-ui.js ... case constants.SHOW_MODAL:

return Object.assign({}, state, {

showModal : true,

modalActions: action.modalActions,

modalTitle : action.title

}); ...

And finally (step 4) the main <App /> shell which is listening to UI state changes, passes the relevant UI state key to the <Modal /> component.

Click to expand image

Full UI Reducer Sample Code

For anybody who is curious, this is a full example of a UI Reducer (the code can probably be shortened).

import constants from 'core/types'; const initialState = {

leftNavOpen : false,

rightNavOpen : false,

showModal : false

}; export function uiReducer(state = initialState, action) {

switch (action.type) { case constants.OPEN_LEFT_NAV:

return Object.assign({}, state, {

leftNavOpen: true

}); case constants.CLOSE_LEFT_NAV:

return Object.assign({}, state, {

leftNavOpen: false

}); case constants.OPEN_RIGHT_NAV:

return Object.assign({}, state, {

rightNavOpen: true

}); case constants.CLOSE_RIGHT_NAV:

return Object.assign({}, state, {

rightNavOpen: false

}); case constants.CLEAR_UI:

return Object.assign({}, state, {

leftNavOpen : false,

rightNavOpen: false,

showModal : false

}); case constants.SHOW_MODAL:

return Object.assign({}, state, {

showModal : true

}); case constants.CLOSE_MODAL:

return Object.assign({}, state, {

showModal : false

}); default:

return state;

}

}

Note how all data that pertains to the UI state of the entire app is controlled here.

Learn How To Develop Professional React Apps

ProfessionalReactApp.com

If you’re interested in getting expert guidance as you develop a professional React app, then check out my training program at ProfessionalReactApp.com.

In the training program, you’ll learn how all the technical pieces of a professional React app are constructed and orchestrated. And you’ll learn how YOU can start developing and designing expert large-scale front-end applications. Front-end applications that you would get paid to develop by either clients, or an employer.

Hope to meet you at the training!