Menu

Using Redux with Flex

In much of your development with the Flex UI, your component state can be informed by information that already lives in Flex - for example, you can get access to the current Task and render the Task data in your custom component.

There are some instances, however, in which adding information to a Task might compromise security, or even just add unnecessary data to the Task itself. In these cases, you can extend the Redux store of your Flex UI and pass the new subscription information to your custom components.

On this page, we’ll cover two strategies for modifying the Flex Redux reducer. The first relies on the Plugin Builder to extend your contact center that’s hosted on flex.twilio.com. The second strategy involves directly modifying the way Flex builds Redux, and is ideal if you’re planning to host Flex on your own infrastructure.

Brief Intro to Redux

Redux is a software package that helps developers manage application state. Flex uses Redux to manage a bunch of application state - for example, a new Task appearing in the UI or an agent changing from Available to Busy are both examples of the application state changing. Redux offers you some nice features:

  • a single source of truth about the state of your application (called the store)
  • an interface for dispatching actions that will update the store
  • an architecture that makes it easy to test and debug state changes, and, perhaps most importantly,
  • an integration with React that allows your React components to subscribe to changes in the store.

Check out the Redux documentation to learn more about all of the great features it offers and become a Redux master. You can also get a useful overview of Redux in the Code Cartoon Intro to Redux.

Using Redux in Your Flex Plugin

When you use the Flex Plugin Builder, you’re already using Redux! The boilerplate plugin code includes all of the bits and pieces that you need to manage the Plugin UI state. You can learn all about building your first plugin here.

Once you have a plugin setup, you can find most of the boilerplate Redux code in src/states/CustomTaskListState.js. For the sake of easily reading this page, the content of the plugin builder files is located in the code samples on this page, so don't stress out if you can't set up your first plugin just yet.

        
        
        
        
        Redux Boilerplate

        CustomTaskListState.js

        Redux Boilerplate

        Defining an Action

        Let's start with the Action that will cause some change in the UI state. In the sample plugin, it’s called ACTION_DISMISS_BAR, and as you might imagine, it dismisses a bar at the top of the UI.

        You'll notice that there's a lot of places that say ACTION_DISMISS_BAR in the code.

        const ACTION_DISMISS_BAR = 'DISMISS_BAR';
        

        In the sample plugin code, we've created a variable called ACTION_DISMISS_BAR assigned it a string with the contents 'DISMISS_BAR'.

        Once the Flex UI is running, Actions will start flying all over the place. Redux needs a unique identifier to understand what all of these Actions are and what they should do to the application state. So, all dispatched actions need a name, and Redux will look up the set of instructions for how it should change state for actions with that particular name. In order to make sure that you don't confuse Redux, the best practice is to use variables, which ensure that the identifier stays consistent in all of the places you're using the action.

        You'll need the Action ID when you're writing your Reducer, Components, and tests. Using a variable for the name allows you to rename the Action REMOVE_BAR by changing it in a single place. You can feel confident that your code will run correctly across the entire code base (and look good in the Redux DevTools - but that's a story for another time!)

        Writing the Reducer

        The reducer is defined with the following function:

        export function reduce(state = initialState, action) {...}
        

        This function returns an updated version of the application based on the dispatched Action.

        So, what is the application state?

        const initialState = {
          isOpen: true,
        };
        

        This Javascript object reflects a small slice of the application state. The Reducer's job is to apply the Action to the current state object to create an updated version of the state. It will then return the updated state to Redux. Redux will later combine this Reducer with a bunch of other reducers to create one big Store of your application state.

        Note that the Reducer returns a fresh copy of the state every time it processes an Action. If you just modify the state without returning it, your application state won't update properly. Learn more about these types of patterns in the Redux Style Guide.

        A Reducer is usually one long switch statement, with the various cases being your different Action names. In this case (ha! See what we did there?) the Reducer deals with two cases: if it sees an Action with the ACTION_DISMISS_BAR identifier, it changes the isOpen value to false. Otherwise, if it doesn't recognize the Action identifier (or there isn't one), it'll just maintain the current application state.

        Adding Plugin State to the Flex Store

        Now that you have this state object, you'll need to make it available to Flex. This happens in src/YourPluginName.js with the addReducer method. This is a method that Flex provides to include your reducer in the Flex store - so this is Flex code, not Redux. It makes your Reducer's state and associated logic available across the Flex UI.

              
              
              
              

              Accessing Redux from your Plugin Component

              Now that your Reducer logic is defined, you need to connect all of that logic to the UI. In the Plugin Builder sample app, this happens in src/components/CustomTaskList/CustomTaskList.Container.js.

                    
                    
                    
                    
                    Container Component Logic

                    CustomTaskList.Container.js

                    Container Component Logic

                    In React, it's common to create a Container component that contains the logic for fetching data for the component and a Presentational component that handles the logic for rendering the component itself. Don't get too hung up on this pattern, but there are lots of resources out there if you're curious about learning more about why the Plugin Sample is structured this way!

                    The code here contains a couple of key pieces of logic:

                    // Import redux methods
                    import { connect } from 'react-redux';
                    import { bindActionCreators } from 'redux';
                    
                    // Import the Redux Actions
                    import { Actions } from '../../states/CustomTaskListState';
                    import CustomTaskList from './CustomTaskList';
                    

                    First, it imports everything that it needs from NodeJS packages and the Action/Component files in the React app

                    const mapStateToProps = (state) => ({
                        isOpen: state['sample'].customTaskList.isOpen,
                    });
                    
                    const mapDispatchToProps = (dispatch) => ({
                      // Note: the plugin's sample code uses a function called bindActionCreators here
                      // For the sake of clarity, we're using the dispatch function directly here
                      dismissBar: dispatch(Actions.dismissBar),
                    });
                    

                    Two functions, mapStateToProps and mapDispatchToProps, are defined. All React components have properties, or props, that you can use to change how the component is displayed. As the name implies, mapStateToProps describes what particular "chunks" of application state the component should know about. In this case, it's defining the value of isOpen.

                    mapDispatchToProps describes the Actions that the component can send to Redux. In this case, the component's dismissBar prop should have a function that can dispatch the DISMISS_BAR action.

                    export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList);
                    

                    Finally, you'll see a fancy connect method. It associates those mapping functions with the presentational React component. Your presentational component's props will now have values defined by those functions.

                    You can see the magic at work in the Component code itself.

                          
                          
                          
                          

                          The component's isOpen prop also has a defined value, thanks to the mapStateToProps function.

                          const CustomTaskList = (props) => {
                            if (!props.isOpen) {
                              return null;
                            }
                          

                          Here, it's being used to conditionally render the component HTML.

                          The component is invoking the dismissBar prop.

                          <i className="accented" onClick={props.dismissBar}>
                          

                          This prop was defined with mapDispatchToProps, and allows the component to dispatch the DISMISS_BAR Action whenever a user clicks the close text.

                          Redux helps you create a flow of data through your whole plugin. The user interacts with the Component, which invokes the dispatch function. The dispatch function sends out the relevant Action to the Reducer. The Reducer takes whatever information is associated with that Action (in this case, toggling isOpen) and modifies the Redux Store to reflect what’s going on. At long last, the Component, which is subscribed to the Store, updates using the power of React, reflecting the new application state!

                          Redux has a complex data flow, but once you've mastered it, it makes reasoning about and testing complex apps - like the UI for an Omnichannel Contact Center - a lot easier.

                          Writing Asynchronous Actions

                          The Flex UI uses the redux-promise middleware for processing asynchronous actions.

                                
                                
                                
                                

                                There are some key terms to call out here:

                                Asynchronous Actions refer to any action code that doesn't immediately return data. For example, in this code, we write a function that, in its simplest description, returns a photo of a dog. We associate this function with an action, with the idea that we could, for example, have a customer click a button to see a cute dog photo.

                                // Fetches a picture of a dog
                                function getDogPhoto() {
                                    return fetch('https://dog.ceo/api/breeds/image/random')
                                        .then((response) => response.json())
                                        .catch((err) => {
                                            return `Error: ${err}`;
                                        });
                                }
                                
                                export const Actions = {
                                // Invoke the getDogPhoto
                                    samplePromise: () => ({
                                        type: 'GET_DOG_PHOTO',
                                        payload: getDogPhoto(),
                                    })
                                };
                                

                                By default, Redux would throw an error with this code, though! This is because our first function doesn't return a dog photo - it returns a Promise for a dog photo. Redux needs an object to return to the reducer, not a Promise for an object!

                                If you need to brush up on your understanding of Promises, check out Mozilla's Promise API Docs to learn more.

                                Promises can resolve in a lot of different ways, and so we need to teach Redux that it should be expecting Promises, and then, for each Promise in our code, we need to define how to handle the various outcomes of the Promise. Enter: our Middleware.

                                The redux-promise middleware allows us to write a reducer that looks like this:

                                export function reduce(state = initialState, action) {
                                  switch (action.type) {
                                    // Action for Redux-Promise-Middleware Pending State
                                    case `${ACTION_GET_DOG_PHOTO}_PENDING`:
                                      return state;
                                    // Action for Redux-Promise-Middleware Success State
                                    case `${ACTION_GET_DOG_PHOTO}_FULFILLED`:
                                      return {
                                        ...state,
                                        image: action.payload.message,
                                      };
                                    // Action for Redux-Promise-Middleware Failed State
                                    case `${ACTION_GET_DOG_PHOTO}_REJECTED`:
                                      return {
                                        ...state,
                                        error: action.payload.error,
                                      };
                                    default:
                                      return state;
                                  }
                                }
                                

                                Instead of just the one GET_DOG_PHOTO action, the reducer handles three actions that handle each Promise state. The middleware can throw a pending action (which is useful for rendering loading spinners and letting people know that something is happening), a fulfilled action if everything goes correctly (here, we use that for adding the dog image to our Redux Store) and a failed action (in case something goes wrong, like the dog photos API is down and we need to show an error.)

                                Because this middleware is already built into the Flex UI's reducer, you'll notice that there's no need to write extra code to import the middleware, or define promise-handling logic yourself. You can just start writing asynchronous code whenever you're ready!

                                Combine Your Application's Reducer and the Flex UI's Reducer

                                If you've built your own Redux application, you can extend your own UI to include all of the stateful goodness in Flex. This option is only recommended if you already have an existing React app - otherwise, Plugins are likely a better choice. The following sample code is a brief example of how you can integrate Flex into your own application.

                                import Flex from "@twilio/flex-ui"
                                import myReducer from './myReducerLocation'
                                
                                // Use Redux's combineReducers method to add your reducer to the Flex reducer
                                const reducers = combineReducers({
                                    flex: Flex.FlexReducer,
                                    app: myReducer
                                });
                                
                                const middleware = applyMiddleware();
                                
                                // Redux builts a store using the reducers and applies Flex middleware
                                const store = createStore(
                                    reducers,
                                    compose(
                                        middleware,
                                        Flex.applyFlexMiddleware()
                                    )
                                );
                                
                                // Flex is instantiated with the new store, which includes your custom reducer
                                Flex
                                    .Manager.create(configuration, store)
                                    .then(manager => {     
                                        ReactDOM.render(
                                            <Provider store={store}>
                                                <Flex.ContextProvider manager={manager}>
                                                    <Flex.RootContainer />
                                                </Flex.ContextProvider>
                                            </Provider>,
                                            container
                                        );
                                    })
                                

                                What Next?

                                Rate this page:

                                Need some help?

                                We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

                                Thank you for your feedback!

                                We are always striving to improve our documentation quality, and your feedback is valuable to us. How could this documentation serve you better?

                                Sending your feedback...
                                🎉 Thank you for your feedback!
                                Something went wrong. Please try again.

                                Thanks for your feedback!

                                Refer us and get $10 in 3 simple steps!

                                Step 1

                                Get link

                                Get a free personal referral link here

                                Step 2

                                Give $10

                                Your user signs up and upgrade using link

                                Step 3

                                Get $10

                                1,250 free SMSes
                                OR 1,000 free voice mins
                                OR 12,000 chats
                                OR more