How To Manage UI State With Redux

June 26, 2017 0 Comments

How To Manage UI State With Redux

 

 

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.

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”.

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 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.

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.

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).

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.

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.SHOWMODAL,
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.SHOWMODAL:
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

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.OPENLEFTNAV:
return Object.assign({}, state, {
leftNavOpen: true
});
case constants.CLOSELEFTNAV:
return Object.assign({}, state, {
leftNavOpen: false
});
case constants.OPENRIGHTNAV:
return Object.assign({}, state, {
rightNavOpen: true
});
case constants.CLOSERIGHTNAV:
return Object.assign({}, state, {
rightNavOpen: false
});
case constants.CLEARUI:
return Object.assign({}, state, {
leftNavOpen : false,
rightNavOpen: false,
showModal : false
});
case constants.SHOWMODAL:
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.

Don’t worry if you didn’t understand everything in this article. It takes some practice to get familiar with some of the newer libraries and patterns for developing apps available on the Web today.

But do note that learning how to write apps with React & Redux is a powerful skill for Front-End Engineers. That’s because you’re responsible for building everything the user sees, touches, and interacts with.

That’s why I created the course How to Write a Single Page Application, an 8 part video course that teaches you the basics of React, Redux, React-Router, and how to think and develop in encapsulated UI components. The course shows you how a professional app is architected (because learning from boring To-Do tutorials isn’t sufficient).

My next Webinar is on July 6, 2017, 10:00am PST/1:00pm EST. Please register for the free online training here because I have a lot of awesome things to share that will help you expand as a developer.

If you attend the Webinar and stay until the end, you’ll receive a 50% discount code for my course.

My name is Mark, and I’m a developer in the San Francisco Bay Area. I’m a huge fan of the Web and am doing what I can to help move it forward.


Tag cloud