Skip to content

Gerenciamento de Estado (Redux & Saga)

Em um ecossistema de saúde com múltiplos módulos, um estado global monolítico torna-se um gargalo de performance e manutenção. Nossa arquitetura utiliza Redux (Toolkit) e Redux-Saga com uma estratégia de Injeção Dinâmica, garantindo que a memória e o processamento sejam utilizados de forma inteligente.

Injeção Dinâmica (Dynamic Injection)

Diferente de aplicações tradicionais onde todos os Reducers e Sagas são combinados no momento da criação da Store, nós utilizamos um padrão de "Plug-and-Play". Cada módulo é responsável por injetar sua própria lógica de estado apenas quando o usuário entra em seu contexto.

Por que usar injeção dinâmica em sistemas médicos?

  1. Performance em Dispositivos Móveis: Médicos e pacientes muitas vezes acessam o sistema via conexões instáveis ou dispositivos com memória limitada. Carregar apenas o necessário reduz drasticamente o tempo de interatividade (TTI).
  2. Modularidade Extrema: Um desenvolvedor trabalhando no módulo de Agendamento não precisa se preocupar se o estado do Prontuário está crescendo ou causando conflitos de nomes.
  3. Isolamento de Erros: Um erro catastrófico em um Reducer injetado dinamicamente pode, em muitos casos, ser isolado sem derrubar a store global de outros módulos ativos.

Fluxo de Injeção no Ciclo de Vida do Componente

Utilizamos hooks de infraestrutura (packages/core/store-injector) para gerenciar esse ciclo. Quando o módulo de "Prescrição Médica" é montado, ele registra seus Reducers e Sagas. Quando desmontado, ele pode (opcionalmente) limpar seu estado para liberar memória.

typescript
import { useInjectReducer, useInjectSaga } from 'core/store-injector';
import { prescriptionReducer } from './store/slice';
import { prescriptionSaga } from './store/sagas';

/**
 * Módulo de Prescrição Médica
 * Este componente orquestra a injeção de sua própria lógica de estado.
 */
const PrescriptionModule = () => {
  // Injeta o Reducer na chave 'prescription' da Store Global
  useInjectReducer({ key: 'prescription', reducer: prescriptionReducer });
  
  // Injeta o Saga para gerenciar efeitos colaterais (chamadas de API, etc)
  useInjectSaga({ key: 'prescription', saga: prescriptionSaga });

  return <PrescriptionView />;
};

Exemplo Detalhado de Store de Domínio

Para garantir a consistência, cada módulo implementa seu estado seguindo o padrão abaixo, utilizando Redux Toolkit.

1. Slice (Estado e Reducers)

O Slice define o "como" os dados são armazenados. No contexto médico, priorizamos estados carregando indicadores de loading e error por entidade.

typescript
// packages/modules/consultation/store/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Consultation } from 'contracts/consultation';

interface ConsultationState {
  activeConsultation: Consultation | null;
  loading: boolean;
  error: string | null;
}

const initialState: ConsultationState = {
  activeConsultation: null,
  loading: false,
  error: null,
};

export const consultationSlice = createSlice({
  name: 'consultation',
  initialState,
  reducers: {
    fetchConsultationRequest: (state, _action: PayloadAction<string>) => {
      state.loading = true;
      state.error = null;
    },
    fetchConsultationSuccess: (state, action: PayloadAction<Consultation>) => {
      state.activeConsultation = action.payload;
      state.loading = false;
    },
    fetchConsultationFailure: (state, action: PayloadAction<string>) => {
      state.loading = false;
      state.error = action.payload;
    },
  },
});

export const { actions: consultationActions, reducer: consultationReducer } = consultationSlice;

2. Selectors (Acesso aos Dados)

Os seletores encapsulam a estrutura da store. Se a chave da store mudar de activeConsultation para currentSession, apenas o seletor precisa ser atualizado, mantendo a UI intacta.

typescript
// packages/modules/consultation/store/selectors.ts
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'core/store/types';

const selectConsultationDomain = (state: RootState) => state.consultation;

export const selectActiveConsultation = createSelector(
  selectConsultationDomain,
  (consultation) => consultation?.activeConsultation
);

export const selectIsConsultationLoading = createSelector(
  selectConsultationDomain,
  (consultation) => consultation?.loading || false
);

3. Sagas (Efeitos Colaterais)

Os Sagas gerenciam a complexidade assíncrona. Eles são ideais para fluxos que exigem múltiplas etapas, como validar uma prescrição antes de enviá-la para a API.

typescript
// packages/modules/consultation/store/sagas.ts
import { call, put, takeLatest } from 'redux-saga/effects';
import { consultationActions } from './slice';
import { consultationApi } from '../api';

function* handleFetchConsultation(action: ReturnType<typeof consultationActions.fetchConsultationRequest>) {
  try {
    const data = yield call(consultationApi.getById, action.payload);
    yield put(consultationActions.fetchConsultationSuccess(data));
  } catch (error: any) {
    yield put(consultationActions.fetchConsultationFailure(error.message));
  }
}

export function* consultationSaga() {
  yield takeLatest(consultationActions.fetchConsultationRequest.type, handleFetchConsultation);
}

Organização Interna do Módulo

Para manter a consistência entre todos os módulos (Consultas, Chat, Exames), seguimos uma estrutura interna padronizada para o estado:

  • slice.ts: Utiliza o Redux Toolkit para definir o estado inicial e as ações. Focamos em estados normalizados para facilitar a atualização de registros médicos.
  • selectors.ts: Encapsula a estrutura do estado. Os componentes nunca acessam state.prescription.data diretamente; eles usam selectPrescriptionData(state). Isso permite mudar a estrutura da store sem quebrar a UI.
  • sagas.ts: Gerencia o fluxo assíncrono. No contexto médico, sagas são vitais para garantir a ordem de operações críticas, como salvar um prontuário antes de gerar o PDF da receita.
  • types.ts: Define as interfaces TypeScript para o estado, garantindo que não existam propriedades nulas inesperadas durante o atendimento.