Como usar Redux com Flex
Em grande parte do seu desenvolvimento com o Flex UI (IU do Flex), o estado do componente pode ser informado por dados que já estão no Flex; por exemplo, você pode obter acesso à tarefa atual e renderizar os dados da tarefa em seu componente personalizado.
No entanto, existem algumas instâncias em que adicionar informações a uma tarefa pode comprometer a segurança ou até mesmo apenas incluir dados desnecessários à própria tarefa. Nesses casos, você pode estender o armazenamento Redux do seu Flex UI (IU do Flex) e passar as novas informações de assinatura para seus componentes personalizados.
Nesta página, abordaremos duas estratégias para modificar o redutor Redux do Flex. A primeira estratégia conta com o Plugin Builder para estender seu contact center hospedado no flex.twilio.com. A segunda estratégia envolve modificar diretamente a maneira como o Flex constrói o Redux e é ideal se você planeja hospedar o Flex na sua própria infraestrutura.
Breve introdução ao Redux
Redux é um pacote de software que ajuda os desenvolvedores a gerenciar o estado do aplicativo. O Flex usa o Redux para gerenciar vários estados do aplicativo; por exemplo, uma nova tarefa que aparece na IU ou um agente que muda de Available
para Busy
são exemplos de alteração do estado do aplicativo. O Redux oferece alguns recursos interessantes:
- uma única fonte de verdade sobre o estado do seu aplicativo (chamada de loja)
- uma interface para enviar ações que atualizarão a loja
- uma arquitetura que facilita o teste e a depuração de alterações de estado e, talvez o mais importante,
- uma integração com React que permite que os componentes React assinem as alterações na loja.
Confira a documentação do Redux para saber mais sobre todos os excelentes recursos que ele oferece e se tornar um mestre do Redux. Você também pode obter uma visão geral útil do Redux no Code Cartoon Introdução ao Redux.
Como usar o Redux em seu plugin do Flex
Ao usar o Plugin Builder do Flex, você já estará usando o Redux! O código do plugin padrão inclui todos os bits e partes de que você precisa para gerenciar o estado da IU do plugin. Você pode aprender tudo sobre como criar seu primeiro plugin aqui.
Depois de configurar o plugin, você pode encontrar a maior parte do código do padrão Redux em src/states/CustomTaskListState.js
. Para facilitar a leitura desta página, o conteúdo dos arquivos do criador de plugins está localizado nos exemplos de código nesta página; portanto, não se preocupe se você ainda não conseguir configurar seu primeiro plugin.
Definir uma ação
Vamos começar com a ação que causará alguma alteração no estado da IU. No plugin de amostra, ela é chamada de ACTION_DISMISS_BAR
, e como você pode imaginar, ela dispensa uma barra na parte superior da interface do usuário.
Você notará que há muitos lugares que dizem ACTION_DISMISS_BAR
no código.
const ACTION_DISMISS_BAR = 'DISMISS_BAR';
No exemplo de código de plugin, criamos uma variável chamada ACTION_DISMISS_BAR
, atribuímos a ela uma string com o conteúdo 'DISMISS_BAR'
.
Assim que o Flex UI (IU do Flex) estiver em execução, as ações começarão a acontecer por todo o lugar. O Redux precisa de um identificador exclusivo para entender o que são todas essas ações e o que elas devem fazer para o estado do aplicativo. Portanto, todas as ações enviadas precisam de um nome, e o Redux procurará o conjunto de instruções sobre como ele deve mudar de estado para ações com esse nome específico. Para não confundir o Redux, a melhor prática é usar variáveis, que garantem que o identificador permaneça consistente em todos os lugares em que você estiver usando a ação.
Você precisará do ID da ação quando estiver escrevendo o redutor, os componentes e os testes. O uso de uma variável para o nome permite renomear a ação REMOVE_BAR
alterando‐a em um único local. Você pode se sentir confiante de que seu código será executado corretamente em toda a base de código (e ficará bem no Redux DevTools, mas essa é uma história para outro momento!)
Gravar o redutor
O redutor é definido com a seguinte função:
export function reduce(state = initialState, action) {...}
Essa função retorna uma versão atualizada do aplicativo com base na ação enviada.
Então, qual é o estado do aplicativo?
const initialState = {
isOpen: true,
};
Este objeto Javascript reflete uma pequena parte do estado do aplicativo. O trabalho do redutor é aplicar a ação ao objeto de estado atual para criar uma versão atualizada do estado. Em seguida, ele retornará o estado atualizado para o Redux. O Redux combinará posteriormente este redutor com um grupo de outros redutores para criar um grande armazenamento do estado do seu aplicativo.
Observe que o redutor returns
uma nova cópia do estado sempre que processa uma ação. Se você apenas modificar o estado sem retorná‐lo, o estado do aplicativo não será atualizado corretamente. Saiba mais sobre esses tipos de padrões no Guia de estilo do Redux.
Um redutor geralmente é uma instrução switch longa, com os vários case
s sendo seus diferentes nomes de ação. Nesse caso (ah! Viu o que fizemos lá?), o redutor lida com dois casos: se vir uma ação com o identificador ACTION_DISMISS_BAR
, ele altera o valor isOpen
para false
. Caso contrário, se ele não reconhecer o identificador da ação (ou não houver um), ele apenas manterá o estado atual do aplicativo.
Adicionar o estado do plugin ao armazenamento Flex
Agora que você tem esse objeto de estado, precisará disponibilizá‐lo para o Flex. Isso ocorre em src/YourPluginName.js
com o método addReducer
. Esse é um método que o Flex fornece para incluir seu redutor na loja do Flex, então esse é o código Flex, não o Redux. Ele disponibiliza o estado do redutor e a lógica associada no Flex UI (IU do Flex).
Acessar o Redux a partir do componente do plugin
Agora que a lógica do redutor está definida, você precisa conectar toda essa lógica à IU. No app de exemplo do Plugin Builder, isso ocorre em src/components/CustomTaskList/CustomTaskList.Container.js
.
No React, é comum criar um componente de contêiner que contenha a lógica para buscar dados para o componente e um componente de apresentação que manipule a lógica para renderizar o próprio componente. Não se prenda muito a esse padrão, mas há muitos recursos disponíveis se você tiver curiosidade de saber mais sobre por que o exemplo do plugin está estruturado dessa maneira!
O código aqui contém algumas partes principais da lógica:
// 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';
Primeiro, ele importa tudo de que precisa dos pacotes NodeJS e os arquivos Action/Component no 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),
});
Duas funções, mapStateToProps
e mapDispatchToProps
, são definidas. Todos os componentes do React têm propriedades, ou props
, que você pode usar para alterar como o componente é exibido. Como o nome indica, mapStateToProps
descreve quais "fragmentos" específicos do estado do aplicativo que o componente deve conhecer. Nesse caso, ele está definindo o valor de isOpen
.
mapDispatchToProps
descreve as ações que o componente pode enviar para o Redux. Nesse caso, a proposta do componente dismissBar
deve ter uma função que possa enviar a ação DISMISS_BAR
.
export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList);
Por fim, você verá um método connect
sofisticado. Ele associa essas funções de mapeamento ao componente React de apresentação. Os componentes de apresentação props
agora terão valores definidos por essas funções.
Você pode ver a magia acontecer no próprio código do componente.
A proposta do componente isOpen
também tem um valor definido, graças à função mapStateToProps
.
const CustomTaskList = (props) => {
if (!props.isOpen) {
return null;
}
Aqui, ele está sendo usado para renderizar condicionalmente o componente HTML.
O componente está invocando a proposta dismissBar.
<i className="accented" onClick={props.dismissBar}>
Essa proposta foi definida com mapDispatchToProps
e permite que o componente envie a ação DISMISS_BAR
sempre que um usuário clicar no texto close
.
O Redux ajuda você a criar um fluxo de dados por meio de todo o plugin. O usuário interage com o componente, que chama a função dispatch. A função dispatch envia a ação relevante ao redutor. O redutor pega qualquer informação associada a essa ação (nesse caso, alternando isOpen
) e modifica o armazenamento do Redux para refletir o que está acontecendo. Finalmente, o componente, que está inscrito na loja, é atualizado usando o poder do React, refletindo o novo estado do aplicativo!
O Redux tem um fluxo de dados complexo, mas depois de dominá‐lo, ele faz com que o raciocínio e o teste de aplicativos complexos, como a IU para um Contact Center omnichannel, seja muito mais fácil.
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;
Gravar ações assíncronas
O Flex UI (IU do Flex) usa o middleware redux-promise para processar ações assíncronas.
Há alguns termos principais a serem indicados aqui:
Ações assíncronas referem‐se a qualquer código de ação que não retorna dados imediatamente. Por exemplo, nesse código, escrevemos uma função que, em sua descrição mais simples, retorna uma foto de um cão. Associamos essa função a uma ação, com a ideia de que poderíamos, por exemplo, fazer com que um cliente clique em um botão para ver uma foto de um cachorrinho fofo.
// 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(),
})
};
Por padrão, o Redux lançaria um erro com esse código! Isso ocorre porque nossa primeira função não retorna uma foto de cachorro, ela retorna a Promessa de uma foto de cachorro. O Redux precisa de um objeto para retornar ao redutor, não a Promessa de um objeto!
Se você precisar melhorar seu entendimento das promessas, confira o documento API Promise do Mozilla para saber mais.
As promessas podem ser resolvidas de várias maneiras diferentes e, portanto, precisamos ensinar ao Redux que ele deve estar esperando promessas e, em seguida, para cada Promessa em nosso código, precisamos definir como lidar com os vários resultados da Promessa. Digite: our Middleware.
O middleware redux-promise
nos permite escrever um redutor parecido com 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;
}
}
Em vez de apenas uma ação GET_DOG_PHOTO
, o redutor lida com três ações que lidam com cada estado de promessa. O middleware pode lançar uma ação pendente (que é útil para renderizar os controles giratórios de carregamento e informar às pessoas que algo está acontecendo), uma ação cumprida se tudo funcionar corretamente (aqui, usamos isso para adicionar a imagem do cão à nossa loja Redux) e uma ação com falha (no caso de algo dar errado, como a API de fotos de cachorro estar inativa e precisarmos mostrar um erro.)
Como esse middleware já está integrado ao redutor da Flex IU (IU do Flex), você notará que não há necessidade de escrever, por conta própria, código adicional para importar o middleware ou definir a lógica de controle de promessas. Você pode começar a escrever código assíncrono sempre que estiver pronto!
Combinar o redutor do seu aplicativo e o redutor da Flex IU (IU do Flex)
Se você criou seu próprio aplicativo Redux, pode estender sua própria IU para incluir todos os benefícios com informações de estado no Flex. Essa opção só é recomendada se você já tiver um app React existente; caso contrário, os plugins provavelmente serão uma opção melhor. A amostra de código a seguir é um breve exemplo de como você pode integrar o Flex ao seu próprio aplicativo.
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
);
})
O que vem a seguir?
- Agora que você já conhece o Redux, saiba mais sobre como o Flex usa o React
- Confira como buscar dados de um plugin e adicione esses dados à sua loja do Redux.
- Saiba como implantar seu plugin
Precisa de ajuda?
Às vezes, todos nós precisamos; a programação é difícil. Receba ajuda agora da nossa equipe de suporte, ou confie na sabedoria da multidão navegando pelo Stack Overflow Collective da Twilio ou buscando a tag Twilio no Stack Overflow.