Redux: separando efeitos colaterais (como redirecionamentos) de ações em componentes

Ao longo do desenvolvimento, especialmente quando novas tecnologias são introduzidas (mais notavelmente neste caso: React), uma luta comum tende a estar relacionada à Separação de Preocupações. Isso está relacionado à nossa capacidade de garantir que cada parte de nosso ecossistema seja responsável apenas pelo que é absolutamente necessário para funcionar corretamente, garantindo a delegação de outras funcionalidades, como efeitos colaterais, às suas partes responsáveis.

Redux e React mudaram a maneira como pensamos sobre programação, design de sites, aplicativos e desenvolvimento de software em geral. É parte de um movimento muito maior em direção à componentização e modularização de softwares. Infelizmente, os aplicativos escritos em React e Redux estão longe de ser perfeitos em relação à Separation of Concerns, embora a evolução das ideias FLUX do Facebook certamente tenha ajudado em muitas áreas.

O problema

Mais recentemente, um item específico no qual tenho trabalhado bastante nunca está tendo redirecionamentos em suas ações . Isso é importante porque, se escrito corretamente, você deve ser capaz de chamar qualquer ação em qualquer componente sem se preocupar com quaisquer efeitos colaterais graves e impactantes. Os efeitos colaterais (como redirecionamentos) sempre devem ocorrer dentro do componente, porque é contextual ao que você está fazendo e POR QUE você está chamando essa ação no momento.

O exemplo que vou usar é uma user switcherfuncionalidade em um sistema de gerenciamento. Durante o login, os Criadores de ação devem ser chamados para estabelecer em qual usuário nossas ações serão realizadas. No entanto, no caso de uma funcionalidade em um ambiente multiusuário, a capacidade de fazer isso rapidamente sem ter que se preocupar com redirecionamentos é importante, especialmente se exigir que o usuário navegue de volta para onde estava.User Switcher

Preparado para o futuro

Para preparar essa funcionalidade para o futuro, precisamos ter certeza de que podemos chamar essa ação em qualquer cenário, sem ter que nos preocupar com isso nos enviando para uma página do Painel (porque isso pode nem sempre ser um efeito colateral pretendido, em qualquer contexto pode ser usado em).

No caso do que está sendo construído agora, entretanto, é um efeito colateral esperado. Ao abstrair isso para o componente, podemos garantir a separação de interesses e ainda tornar cada pequeno pedaço de nosso aplicativo o mais versátil e flexível possível.

Após alguns testes com , para minha surpresa, a funcionalidade resultante é exatamente o que precisamos para garantir esses recursos.redux-thunk

A implementação

Primeiro, a ação é adaptada para retornar uma resposta previsível: uma promessa com a carga útil de despacho. Ao usar tipos de ação específicos, podemos lançar uma rede estreita para garantir que os efeitos colaterais sejam garantidos com base em cenários específicos. Isso inclui respostas bem-sucedidas e erradas.

export const switchUser = (targetUser) => (dispatch) => {
dispatch
({ type: SWITCH_USER_REQUESTED });

return callSwitchUser(targetUser)
.then(r => r.data)
.then(payload => ({ type: SWITCH_USER_SUCCESSFUL, payload }))
.catch(error => ({ type: SWITCH_USER_FAILED, payload: error }))
.then(dispatch);
};

Observe como estamos retornando a promessa que usamos para despachar.

Em seguida, também atualizando nossos mapDispatchToPropsutilitários, podemos garantir que a promessa seja transmitida a nós dentro de nosso componente para que possamos utilizá-la significativamente dentro do escopo restrito de nosso componente, ao contrário de dentro de nossas ações ou outras áreas de nossa aplicação .

// ...

const mapDispatchToProps = dispatch => ({
switchUser
: (...args) => dispatch(switchUserAction(...args))
});

// ...

Com esse passthrough habilitado (observe como switchUser retorna dispatch(...) ), uma chamada para switchUser()dentro de nosso componente sempre retornará uma promessa (visto que garantimos nossos valores de retorno uniformes e previsíveis em nossas ações).

switchUser(targetUserId) {
const { switchUser, redirect } = this.props;

// Change the current user
switchUser
(targetUserId)
// Throw if switching user failed
.then(({ type, payload }) => type === 'SWITCH_USER_FAILED' && throw payload)
// Otherwise redirect to dashboard page
.then(() => redirect('/dashboard'));
// Catch thrown error and console it!
.catch((e) => console.error('Switching the user failed!', e))
}

Isso abre um mundo inteiramente novo de efeitos colaterais de composição com base nas circunstâncias, mas garante que o componente, que é o consumidor da ação, é responsável por lidar com os efeitos colaterais.