A abordagem que sugiro é um pouco detalhada, mas achei que ela pode se adaptar muito bem a aplicativos complexos. Quando você quiser mostrar um modal, inicie uma ação descrevendo qual modal você gostaria de ver:
Despachando uma ação para mostrar o modal
this.props.dispatch({
type: 'SHOW_MODAL',
modalType: 'DELETE_POST',
modalProps: {
postId: 42
}
})
(As strings podem ser constantes, é claro; estou usando strings inline para simplificar.)
Escrevendo um redutor para gerenciar o estado modal
Em seguida, verifique se você possui um redutor que aceite apenas esses valores:
const initialState = {
modalType: null,
modalProps: {}
}
function modal(state = initialState, action) {
switch (action.type) {
case 'SHOW_MODAL':
return {
modalType: action.modalType,
modalProps: action.modalProps
}
case 'HIDE_MODAL':
return initialState
default:
return state
}
}
/* .... */
const rootReducer = combineReducers({
modal,
/* other reducers */
})
Ótimo! Agora, quando você despacha uma ação,state.modal
será atualizada para incluir as informações sobre a janela modal atualmente visível.
Escrevendo o componente modal raiz
Na raiz da sua hierarquia de componentes, adicione um <ModalRoot>
componente conectado ao repositório Redux. Ele escutará state.modal
e exibirá um componente modal apropriado, encaminhando os adereços a partir do state.modal.modalProps
.
// These are regular React components we will write soon
import DeletePostModal from './DeletePostModal'
import ConfirmLogoutModal from './ConfirmLogoutModal'
const MODAL_COMPONENTS = {
'DELETE_POST': DeletePostModal,
'CONFIRM_LOGOUT': ConfirmLogoutModal,
/* other modals */
}
const ModalRoot = ({ modalType, modalProps }) => {
if (!modalType) {
return <span /> // after React v15 you can return null here
}
const SpecificModal = MODAL_COMPONENTS[modalType]
return <SpecificModal {...modalProps} />
}
export default connect(
state => state.modal
)(ModalRoot)
O que fizemos aqui? ModalRoot
lê a corrente modalType
e modalProps
da state.modal
qual está conectada e renderiza um componente correspondente, como DeletePostModal
ouConfirmLogoutModal
. Todo modal é um componente!
Escrevendo componentes modais específicos
Não há regras gerais aqui. Eles são apenas componentes do React que podem despachar ações, ler algo do estado da loja e são modais .
Por exemplo, DeletePostModal
pode parecer com:
import { deletePost, hideModal } from '../actions'
const DeletePostModal = ({ post, dispatch }) => (
<div>
<p>Delete post {post.name}?</p>
<button onClick={() => {
dispatch(deletePost(post.id)).then(() => {
dispatch(hideModal())
})
}}>
Yes
</button>
<button onClick={() => dispatch(hideModal())}>
Nope
</button>
</div>
)
export default connect(
(state, ownProps) => ({
post: state.postsById[ownProps.postId]
})
)(DeletePostModal)
O DeletePostModal
está conectado à loja para exibir o título da postagem e funciona como qualquer componente conectado: pode despachar ações, incluindohideModal
quando é necessário se esconder.
Extraindo um componente de apresentação
Seria estranho copiar e colar a mesma lógica de layout para cada modal "específico". Mas você tem componentes, certo? Então você pode extrair uma apresentação <Modal>
componente de que não sabe o que modais específicos fazem, mas lida com a aparência deles.
Em seguida, modais específicos, como DeletePostModal
podem ser usados para renderização:
import { deletePost, hideModal } from '../actions'
import Modal from './Modal'
const DeletePostModal = ({ post, dispatch }) => (
<Modal
dangerText={`Delete post ${post.name}?`}
onDangerClick={() =>
dispatch(deletePost(post.id)).then(() => {
dispatch(hideModal())
})
})
/>
)
export default connect(
(state, ownProps) => ({
post: state.postsById[ownProps.postId]
})
)(DeletePostModal)
Cabe a você criar um conjunto de adereços que <Modal>
possam ser aceitos em seu aplicativo, mas eu imagino que você possa ter vários tipos de modais (por exemplo, informação modal, confirmação de confirmação etc.) e vários estilos para eles.
Acessibilidade e ocultação ao clicar fora ou na tecla Escape
A última parte importante sobre os modais é que geralmente queremos ocultá-los quando o usuário clica fora ou pressiona Escape.
Em vez de dar conselhos sobre como implementar isso, sugiro que você não o implemente. É difícil acertar considerando a acessibilidade.
Em vez disso, sugiro que você use um componente modal acessível disponível no mercado, como react-modal
. É completamente personalizável, você pode colocar o que quiser dentro dele, mas lida com a acessibilidade corretamente para que pessoas cegas ainda possam usar o seu modal.
Você pode embrulhar react-modal
você mesmo <Modal>
que aceita objetos específicos para seus aplicativos e gera botões filhos ou outro conteúdo. É tudo apenas componentes!
Outras abordagens
Há mais de uma maneira de fazer isso.
Algumas pessoas não gostam da verbosidade dessa abordagem e preferem ter um <Modal>
componente que possa renderizar diretamente dentro de seus componentes com uma técnica chamada "portais". Os portais permitem renderizar um componente dentro do seu enquanto na verdade ele será renderizado em um local predeterminado no DOM, o que é muito conveniente para os modais.
De fato, ao qual react-modal
eu vinculei anteriormente, já faz isso internamente, tecnicamente, você nem precisa renderizá-lo do topo. Ainda acho bom desacoplar o modal que quero mostrar do componente que o mostra, mas você também pode usar react-modal
diretamente de seus componentes e pular a maior parte do que escrevi acima.
Convido você a considerar as duas abordagens, experimentar com elas e escolher o que achar melhor para seu aplicativo e sua equipe.