You are viewing the Spanish (Mexico) site, but your language preference is set to English. Switch to English site →

Menu

Cómo usar Redux con Flex

Durante gran parte del desarrollo con Flex UI (la interfaz de usuario de Flex), el estado de tus componentes puede utilizar la información que ya está disponible en Flex, por ejemplo, puedes obtener acceso a la tarea actual y mostrar los datos de la tarea en tu componente personalizado.

Sin embargo, hay algunos casos en los que agregar información a una tarea podría comprometer la seguridad o incluso agregar datos innecesarios a la propia tarea. En estos casos, puedes extender el almacén Redux de la interfaz de usuario de Flex y pasar la nueva información de suscripción a los componentes personalizados.

En esta página, abordaremos dos estrategias para modificar el reductor de Redux para Flex. La primera depende del generador de plugins para extender el contact center alojado en flex.twilio.com. La segunda estrategia implica modificar directamente la forma en que Flex compila Redux, y es ideal si planeas alojar Flex en tu propia infraestructura.

Breve introducción a Redux

Redux es un paquete de software que ayuda a los desarrolladores a administrar el estado de la aplicación. Flex utiliza Redux para administrar un grupo de estados de la aplicación. Algunos ejemplos del cambio de estado de la aplicación son una nueva tarea que aparece en la interfaz de usuario o un agente que pasa del estado Available al estado Busy. Redux te ofrece algunas funcionalidades útiles:

  • una única fuente de información acerca del estado de la aplicación (denominada store [almacén]);
  • una interfaz para despachar acciones que actualizarán el almacén;
  • una arquitectura que facilita la prueba y depuración de los cambios de estado; y, quizás lo más importante,
  • una integración en React que permite a los componentes de React suscribirse a los cambios del almacén.

Consulta la documentación de Redux para obtener más información sobre todas las excelentes funcionalidades que ofrece y convertirte en un experto en Redux. También puedes obtener una visión general útil de Redux en la introducción a Redux mediante dibujos.

Cómo usar Redux en tu plugin de Flex

Cuando usas el generador de plugins de Flex, ya estás usando Redux. El código repetitivo del plugin incluye todo lo que necesitas para administrar el estado de la interfaz de usuario del plugin. Puedes aprender todo lo que necesitas para crear tu primer plugin aquí.

Una vez que hayas configurado el plugin, puedes encontrar la mayor parte del código repetitivo de Redux en src/states/CustomTaskListState.js. Para facilitar la lectura de esta página, el contenido de los archivos del generador de plugins se encuentra en los ejemplos de código de esta página, así que mantén la calma si todavía no puedes configurar tu primer plugin.

        
        
        

        CustomTaskListState.js

        Definir una acción

        Comencemos con una acción que provoque algún cambio en el estado de la interfaz de usuario. En el plugin de ejemplo, se llama ACTION_DISMISS_BAR, y como puedes imaginar, se encarga de descartar una barra en la parte superior de la interfaz de usuario.

        Notarás que en muchas partes del código dice ACTION_DISMISS_BAR.

        const ACTION_DISMISS_BAR = 'DISMISS_BAR';

        En el código de ejemplo del plugin, creamos una variable llamada ACTION_DISMISS_BAR, le asignamos una cadena con el contenido 'DISMISS_BAR'.

        Una vez que se ejecute Flex UI (la interfaz de usuario de Flex), las acciones comenzarán a volar por todos lados. Redux necesita un identificador único para comprender qué son todas estas acciones y qué debe hacer con el estado de la aplicación. Por esta razón, todas las acciones despachadas necesitan un nombre, y Redux buscará el conjunto de instrucciones correspondiente para saber cómo debe cambiar el estado cuando se ejecuta una de las acciones con dicho nombre. Para asegurarte de brindar claridad a Redux, la práctica recomendada es utilizar variables, las que garantizan que el identificador sea siempre el mismo en todos los lugares en los que se utilice la acción.

        Necesitarás el identificador de la acción cuando escribas el reductor, los componentes y las pruebas. El uso de una variable para el nombre te permite cambiar el nombre de la acción REMOVE_BAR a nivel global modificándolo en un solo lugar. Puedes estar seguro de que tu código se ejecutará correctamente en todas partes (y se verá bien en Redux DevTools, pero eso es una historia para otro momento).

        Crear el reductor

        El reductor se define con la siguiente función:

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

        Esta función devuelve una versión actualizada de la aplicación basada en la acción despachada.

        Entonces, ¿cuál es el estado de la aplicación?

        const initialState = {
          isOpen: true,
        };

        Este objeto de JavaScript refleja una pequeña porción del estado de la aplicación. El trabajo del reductor consiste en aplicar la acción al objeto de estado actual para crear una versión actualizada del estado. A continuación, devolverá el estado actualizado a Redux. Redux combinará más adelante este reductor con muchos otros reductores para crear un almacén grande del estado de tu aplicación.

        Debes tener en cuenta que el reductor devuelve (returns) una copia nueva del estado cada vez que procesa una acción. Si solo modifica el estado sin devolverlo, el estado de la aplicación no se actualizará correctamente. Obtén más información sobre estos tipos de patrones en la Guía de estilo de Redux.

        Un reductor suele ser una declaración switch larga, cuyos casos (case) corresponden a los diferentes nombres de acciones. En este caso (¡Ja! ¿Entendiste el juego de palabras?) el reductor se ocupa de dos casos: si ve una acción con el identificador ACTION_DISMISS_BAR, cambia el valor de isOpen a false. De lo contrario, si no reconoce el identificador de acción (o este no existe), solo mantendrá el estado actual de la aplicación.

        Agregar el estado del plugin al almacén de Flex

        Ahora que tienes este objeto de estado, tendrás que darle a Flex acceso a él. Esto se hace en src/YourPluginName.js con el método addReducer. Este es un método que Flex proporciona para incluir tu reductor en el almacén de Flex, así que se trata de código de Flex, no de Redux, y te permite brindarle acceso a Flex UI (la interfaz de usuario de Flex) al estado y la lógica asociada del reductor.

              
              
              

              YourPluginName.js

              Acceder a Redux desde el componente del plugin

              Ahora que la lógica del reductor está definida, debes conectar toda esa lógica a la interfaz de usuario. En la app de ejemplo del generador de plugins, esto se hace en src/components/CustomTaskList/CustomTaskList.Container.js.

                    
                    
                    
                    Lógica de componentes de tipo contenedor

                    CustomTaskList.Container.js

                    Lógica de componentes de tipo contenedor

                    En React, es común crear un componente de tipo contenedor que contenga la lógica para obtener los datos que el componente necesita y un componente de presentación que controle la lógica para mostrar el propio componente. Aunque recomendamos que no te detengas demasiado a analizar este patrón, hay muchos recursos allí fuera si tienes curiosidad por aprender más sobre las razones por las que el plugin de ejemplo está estructurado de esta manera.

                    El código que te presentamos aquí contiene un par de elementos lógicos clave:

                    // 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';

                    En primer lugar, importa todo lo que necesita de los paquetes de NodeJS y los archivos de componentes y acciones en la app React

                    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),
                    });

                    Se definen dos funciones: mapStateToProps y mapDispatchToProps. Todos los componentes de React tienen propiedades, o props, que puedes utilizar para cambiar la forma en que se muestra el componente. Como su nombre lo indica, mapStateToProps describe qué "porciones" concretas del estado de la aplicación debe conocer el componente. En este caso, define el valor de isOpen.

                    mapDispatchToProps describe las acciones que el componente puede enviar a Redux. En este caso, la propiedad dismissBar del componente debe tener una función que pueda despachar la acción DISMISS_BAR.

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

                    Por último, verás el elegante método connect que asocia esas funciones de mapeo con el componente React de presentación. Las props del componente de presentación ahora tendrán valores definidos por esas funciones.

                    Puedes ver como se hace la magia en el propio código del componente.

                          
                          
                          

                          CustomTaskList.jsx

                          La propiedad isOpen del componente también tiene un valor definido gracias a la función mapStateToProps.

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

                          Aquí, se utiliza para mostrar de forma condicional el componente HTML.

                          El componente invoca a la propiedad disminupBar.

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

                          Esta propiedad se definió mediante mapDispatchToProps y permite que el componente despache la acción DISMISS_BAR siempre que un usuario haga clic en el texto close.

                          Redux te ayuda a crear un flujo de datos a través de todo el plugin. El usuario interactúa con el componente, que invoca la función de despacho. La función de despacho envía la acción relevante al reductor. El reductor toma la información asociada a esa acción (en este caso, alternar isOpen) y modifica el almacén de Redux para reflejar lo que está ocurriendo. Por último, el componente, que está suscrito al almacén, se actualiza mediante la tecnología de React, y muestra el nuevo estado de la aplicación.

                          Redux tiene un flujo de datos complejo, pero una vez que lo domines, hace que sea mucho más fácil entender y probar apps complejas, como la interfaz de usuario de un contact center omnicanal.

                          Adding State to the Redux Store

                          You can add additional state to your Plugin's component. Inside src/states/CustomTaskListState.js, define your state in the initialState object, as well as a variable that will represent the state's action.

                          const ACTION_DISMISS_BAR = 'DISMISS_BAR';
                          const ACTION_UPDATE_MESSAGE = 'UPDATE_MESSAGE';
                          
                          const initialState = {
                            isOpen: true,
                            message: 'Flex is Awesome!',
                          }

                          Define an Action that will tell Redux how to update your component's state.

                          export class Actions {
                            static dismissBar = () => ({ type: ACTION_DISMISS_BAR });
                            static updateMessage = (value) => ({ type: ACTION_UPDATE_MESSAGE, value });
                          }

                          When your component updates the state, it will dispatch an action which will call the reducer in order to update the state.

                          export function reduce(state = initialState, action) {
                            switch (action.type) {
                              case ACTION_DISMISS_BAR: {
                                return {
                                  ...state,
                                  isOpen: false,
                                };
                              }
                              case ACTION_UPDATE_MESSAGE: {
                                return {
                                  ...state,
                                  message: action.value, // update the message state property with new value stored in action object.
                                };
                              }
                              default:
                                return state;
                            }
                          }

                          Now that you have added a new state value to the initialState object, defined a Redux Action, and handled the ACTION_UPDATE_MESSAGE action by implementing a case inside the reducer function, you need to map the state properties and dispatch functions to your components props. Doing this will allow you to retrieve the data from the Redux Store, as well as update the state of the Redux Store with new values.

                          Inside src/CustomTaskList/CustomTaskList.Container.js, modify the mapStateToProps method:

                          // 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';
                          
                          // Define mapping functions
                          const mapStateToProps = (state) => ({
                              isOpen: state['sample'].customTaskList.isOpen,
                              message: state['sample'].customTaskList.message, // maps the message state property to component's props
                          });
                          
                          const mapDispatchToProps = (dispatch) => ({
                            dismissBar: bindActionCreators(Actions.dismissBar, dispatch),
                            updateMessage: bindActionCreators(Actions.updateMessage, dispatch), // maps the updateMessage function to the component's props
                          });
                          
                          // Connect presentational component to Redux
                          export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList);
                          

                          Finally, inside src/CustomTaskList/CustomTaskList.jsx, you can reference message and alertMessage as component props.

                          import React from 'react';
                          
                          import { CustomTaskListComponentStyles } from './CustomTaskList.Styles';
                          
                          // It is recommended to keep components stateless and use redux for managing states
                          const CustomTaskList = (props) => {
                            if (!props.isOpen) {
                              return null;
                            }
                          
                            return (
                              <CustomTaskListComponentStyles>
                                This is a dismissible demo component
                                <i className="accented" onClick={props.dismissBar}>
                                  close
                                </i>
                                <span>{props.message}</span>
                                {/* when you type something it will update the message state property */}
                                <input type="text" value={props.message} onChange={(e) => props.updateMessage(e.target.value) } />
                              </CustomTaskListComponentStyles>
                            );
                          };
                          
                          export default CustomTaskList;

                          Escribir acciones asíncronas

                          Flex UI (la interfaz de usuario de Flex) utiliza el middleware redux-promise para procesar acciones asincrónicas.

                                
                                
                                

                                AsyncExample.js

                                Hay algunos términos clave que debemos mencionar:

                                Las acciones asincrónicas son cualquier línea o bloque de código de acción que no devuelve datos de inmediato. Por ejemplo, en este código escribimos una función que, en su descripción más simple, devuelve una foto de un perro. Asociamos esta función a una acción, con la idea de que, por ejemplo, un cliente pueda hacer clic en un botón para ver la foto de un tierno perro.

                                // 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(),
                                    })
                                };

                                Pero, por defecto, Redux arrojaría un error con este código. Esto se debe a que nuestra primera función no devuelve la foto de un perro, sino una promesa para obtener la foto de un perro. Redux necesita un objeto que devolver al reductor, no una promesa para obtener un objeto.

                                Si necesitas repasar tu comprensión de las promesas, consulta la documentación de la API de promesas de Mozilla para obtener más información.

                                Las promesas se pueden resolver de muchas maneras diferentes, por lo que debemos enseñarle a Redux que debe esperar el retorno de una promesa y, luego, debemos definir cómo manejar los diversos resultados de cada promesa en nuestro código. Te presentamos nuestro middleware.

                                El middleware redux-promise nos permite escribir un reductor similar a este:

                                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;
                                  }
                                }

                                En lugar de controlar solo la acción GET_DOG_PHOTO, el reductor controla tres acciones que manejan cada estado de la promesa. El middleware puede lanzar una acción pendiente (que es útil para mostrar spinners de carga y comunicarles a las personas que algo está sucediendo), una acción cumplida, si todo sale bien (aquí, la usamos eso para agregar la imagen del perro a nuestro almacén de Redux) y una acción fallida (en caso de que algo salga mal, por ejemplo, que la API de fotos de perros esté inactiva y debamos mostrar un error).

                                Como este middleware ya está integrado en el reductor de la interfaz de usuario de Flex, te darás cuenta de que no es necesario que escribas código adicional para importar el middleware ni que definas tú mismo la lógica de control de las promesas. Puedes empezar a escribir código asincrónico cuando quieras.

                                Combinar el reductor de tu aplicación y el reductor de la interfaz de usuario de Flex

                                Si creaste tu propia aplicación Redux, puedes extender tu propia interfaz de usuario para incluir todas las características de estado de Flex. Esta opción solo se recomienda si ya tienes una app React; de lo contrario, es probable que los plugins sean la mejor opción. El siguiente código de ejemplo es un breve ejemplo de cómo puedes integrar Flex en tu propia aplicación.

                                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
                                        );
                                    })
                                

                                ¿Qué sigue?

                                Calificar esta página:

                                ¿Necesitas ayuda?

                                Todos la necesitamos a veces; la programación es difícil. Obtén ayuda ahora de nuestro equipo de soporte, o recurre a la sabiduría de la multitud visitando Stack Overflow Collective de Twilio o navegando por la etiqueta de Twilio en Stack Overflow.

                                Gracias por tus comentarios.

                                Selecciona los motivos de tus comentarios. La información adicional que nos brindas nos ayuda a mejorar nuestra documentación:

                                Enviando tus comentarios…
                                🎉 Gracias por tus comentarios.
                                Se produjo un error. Inténtalo de nuevo.

                                Gracias por tus comentarios.

                                thanks-feedback-gif