Solucionado (ver solução)
Solucionado
(ver solução)
7
respostas

React: Reutilização de código

Boa tarde, Estou fazendo uma CRUD para testar a funcionalidade do componente modal ( Bootstrap)

E comecei a reparar que estava digitando muitas vezes o mesmo código, por exemplo :

export default class Banco extends Component {
    constructor() {
        super();
        this.state = {
            modalnovo: false,
            modaldel: false,
            modalalterar: false

        }

    }

Métodos:

    togglemodalnovo = () => {
        this.setState({
            modalnovo: !this.state.modalnovo
        });
    }

    togglemodaldel = () => {
        this.setState({
            modaldel: !this.state.modaldel
        });
    }

    togglemodalalterar = () => {
        this.setState({
            modalalterar: !this.state.modalalterar
        });
    }

// Modal Novo

   <Modal isOpen={this.state.modalnovo} toggle={this.togglemodalnovo}>
                    <ModalHeader className="teste" toggle={this.togglemodalnovo}>Deletar Banco</ModalHeader>
                    <ModalBody className="teste" >
                        <div className="col-lg-6">
                                    <input onChange={(e) => { this.updateProperty('id', e.target.value) }} type="text"></input>
                                </div>
 <div className="col-lg-6">
                                    <input onChange={(e) => { this.updateProperty('nome', e.target.value) }} type="text"></input>
                                </div>
                    </ModalBody>
                    <ModalFooter className="teste">
                        <Button color="danger" onClick={this.delete}>Deletar</Button>
                        <Button color="warning" onClick={this.togglemodaldel}>Cancelar</Button>
                    </ModalFooter>
                </Modal

//Modal deletar

   <Modal isOpen={this.state.modaldel} toggle={this.togglemodaldel}>
                    <ModalHeader className="teste" toggle={this.togglemodaldel}>Deletar Banco</ModalHeader>
                    <ModalBody className="teste" >
                        <h6>Tem certeza que deseja <b className='red-text'>EXCLUIR</b> os itens selecionados:</h6>
                        <BootstrapTable className="mt-3" pagination striped data={this.state.listbancosel}>
                            <TableHeaderColumn id="tableFinanceiro" isKey dataField='id'>ID</TableHeaderColumn>
                            <TableHeaderColumn dataField='nome'>Nome:</TableHeaderColumn>
                            <TableHeaderColumn dataField='conta'>Conta:</TableHeaderColumn>
                            <TableHeaderColumn dataField='agencia'>Agencia:</TableHeaderColumn>
                            <TableHeaderColumn dataField='ativo'>Ativo:</TableHeaderColumn>
                        </BootstrapTable>
                    </ModalBody>
                    <ModalFooter className="teste">
                        <Button color="danger" onClick={this.delete}>Deletar</Button>
                        <Button color="warning" onClick={this.togglemodaldel}>Cancelar</Button>
                    </ModalFooter>
                </Modal

//Modal alterar

   <Modal isOpen={this.state.modalalterar toggle={this.togglemodalalterar}>
                    <ModalHeader className="teste" toggle={this.togglemodalalterar}>Deletar Banco</ModalHeader>
                    <ModalBody className="teste" >
                        <h6>Tem certeza que deseja <b className='red-text'>EXCLUIR</b> os itens selecionados:</h6>
                        <BootstrapTable className="mt-3" pagination striped data={this.state.listbancosel}>
                            <TableHeaderColumn id="tableFinanceiro" isKey dataField='id'>ID</TableHeaderColumn>
                            <TableHeaderColumn dataField='nome'>Nome:</TableHeaderColumn>
                            <TableHeaderColumn dataField='conta'>Conta:</TableHeaderColumn>
                            <TableHeaderColumn dataField='agencia'>Agencia:</TableHeaderColumn>
                            <TableHeaderColumn dataField='ativo'>Ativo:</TableHeaderColumn>
                        </BootstrapTable>
                    </ModalBody>
                    <ModalFooter className="teste">
                        <Button color="danger" onClick={this.modalalterar}>Deletar</Button>
                        <Button color="warning" onClick={this.togglemodalalterar}>Cancelar</Button>
                    </ModalFooter>
                </Modal

Não leve em conta o conteúdo do modal pois estou usando como exemplo. Essa é a maneira correta de lidar com essa situação ? ficar criando um modal diferente para cada ação, CADASTRAR, DELETAR e EXCLUIR.

Teria como criar um componente Modal e importar ele quando for utilizar porem editar algumas coisas, como exemplo, colocar mais campos e alterar cores.

7 respostas
solução!

Fala Denis, tudo bem ?

Você deveria sim melhorar a implementação e você até ja começou a observar bem o que poderia fazer. Perceba que no estado do seu componente Banco (e nas funçoes que eles detem) você apenas descreve o necessário para fazer o toggle dos modais.

Você poderia criar um componente mais alto nivel que isolasse todo o código que se repete no render, as funções idênticas e o estado. Algo assim:

//imports

export default class MyModal extends Component {

    constructor(props) {
        super(props);
        this.state = {
            visible: this.props.visible
        }
    }

    _toggleVisibility = () => {
        // faz o setState invertendo a visibilidade
    }

    render() {
        return (
            <Modal isOpen={this.state.visible} toggle={this._toggleVisibility}>
                // todo o conteúdo necessário
            </Modal>
        );
    }
}

Assim você pode usar de forma mais simples no seu componente Banco:

// imports
import MyModal from './suapastadecomponentes/MyModal.js';

export default class Banco extends Component {

    render() {
        return() {
            // .. qualquer outro código
            <MyModal visible={false} outrasPropsNecessarias... }/>
        }
    }
}

Assim você isola todo o código e ganha mais facilidade na hora de reaproveitar em qualquer outro local. Você pode passar qualquer outra propriedade (props) que façam sentido pro seu componente de modal, inclusive para decidir o que deve ter no titulo, corpo, rodape, etc ao invés de criar um componente pra cada modal (que ainda teriam muita repetição)

Você poderia até usar dessa forma ...

export default class Banco extends Component {

    render() {
        return() {
            // .. qualquer outro código
            <MyModal visible={false}>
                <TituloModal>texto</TituloModal>
                <CorpoModal>
                    // qualquer componente
                </CorpoModal>
            </MyModal>
        }
    }
}

... e definir entre as tags de abertura e fechamento todo o conteúdo que se altera dentro de cada modal, e pegar dentro do render do modal através de this.props.children.

//imports

export default class MyModal extends Component {
    // ...
    render() {
        return (
            <Modal isOpen={this.state.visible} toggle={this._toggleVisibility}>
                // todo o conteúdo necessário

                {this.props.children} // vai aproveitar toda a definição entre as tags aqui
            </Modal>
        );
    }
}

Enfim. Creio que essas ideias vão te ajudar a ter um melhor reaproveitamento dos seus componentes. Agora é com você!

Espero ter ajudado. Abraço!

Boa tarde Rafael, Sua explicação me ajudou muito, pesquisei sobre o

this.props.children

Porem cheguei em mais e mais duvidas...

Por exemplo, meu código esta assim:

Modal:

import { Container, Button, Modal, ModalBody, ModalHeader, ModalFooter } from 'mdbreact';
import React, { Component } from 'react';

export default class Mymodal extends Component {
    constructor(props) {
        super(props);
        this.state = {
            visible: this.props.visible
        }
    }

    _toggleVisibility = () => {
        visible: !this.props.visible
    }


    render(){
        return(
            <Modal isOpen={this.state.visible} toggle={this.__toggleVisibility}>
                <ModalHeader>
                    <ModalBody>

                    </ModalBody>
                </ModalHeader>
            </Modal>
        );
    }
}

App.js:

//import React, { Component } from 'react';
//import Mymodal from './componentes/Mymodal';

 render() {

    return (
      <Container>
        <Mymodal visible={true}>

        </Mymodal>
      </Container>
    );
  }
}

Isso fez com que meu Modal é aberto ao iniciar a pagina, porem agora me vem uma duvida, como posso alterar o conteúdo do Header, ou do Body ?

Se eu for no próprio componente e declarar o conteúdo do Body e Header ele renderiza como esperado. Exemplo:

Mymodal:

// Restante do codigo
  render(){
        return(
            <Modal isOpen={this.state.visible} toggle={this.__toggleVisibility}>
                <ModalHeader>
                    <ModalBody>
                        <h1>Deletar</h1>
<input></input>
                    </ModalBody>
                </ModalHeader>
            </Modal>
        );
    }
}

Porem dessa forma eu volto a ter que fazer um modal para cada ação (Deletar, Gravar, Alterar).

Na teoria eu entendi oque preciso fazer Rafael, você explicou perfeitamente, mas agora na hora de colocar em pratica não estou conseguindo colocar os atributos que quero no modal sem ser diretamente nele.

Fala Denis, blz ?

Antes de pensar sobre a questão do código no render, vou só fazer uma observação. Lembre-se que o código do _toggleVisibility() deve ser corrigido para fazer o setState (você inclusive vinha fazendo certa na primeira versão do seu código)

_toggleVisibility = () => {
        this.setState({ visible: !this.state.visible }); // <-- cuidado aqui: deve ser acessado do state e não do props, para poder inverter o valor presente no estado anterior (que não é o mesmo que props, possivelmente) 
}

Outra observação é que parece ter dois underlines em sequencia na passagem da função para a propriedade toggle: <Modal ... toggle={this.__toggleVisibility} />, enquanto da declaração da função (por convenção temos apenas um).

Agora sim! Vamos pensar sobre o render dos componentes. A ideia aqui é que não existe muito o conceito de certo/errado, existem estratégias que podemos utilizar pra facilitar a manutenção e evitar repetição de código.

A minha dica para você conseguir fazer essa alteração no seu código ser produtiva (sem dar o código, pois julgo importante ser feito esse exercício), é você analisar o código (conteúdo e tags de marcação) dentro de cada Modal que poderíamos criar. (supondo inicialmente a criação de um componente pra cada modal).

Fazendo essa análise, você vai conseguir encontrar quais pontos são os pontos de repetição, similaridade e quais os pontos onde o código muda. Essa é sempre a primeira tarefa. A partir daí a ideia é você manter dentro de um único componente genérico todo o código que se repete pra todos os casos, e tentar receber de fora o conteúdo que muda em cada caso (aí como citado, até o this.props.children pode ajudar, mas não é uma regra, não é obrigado adotar essa estratégia de uso da propriedade children ok?).

Exemplo: Vendo o código dos três modais da pra perceber bastante repetição de código. Vou me ater a só uma delas e você segue com as restantes.

Trecho que eu quero destacar sobre os tres modais:

<Modal isOpen={this.state.modalnovo} toggle={this.togglemodalnovo}>
    <ModalHeader className="teste" toggle={this.togglemodalnovo}>Deletar Banco</ModalHeader>

    ...
    ...

</Modal>

<Modal isOpen={this.state.modaldel} toggle={this.togglemodaldel}>
    <ModalHeader className="teste" toggle={this.togglemodaldel}>Deletar Banco</ModalHeader>

    ...
    ...

</Modal>

<Modal isOpen={this.state.modalalterar toggle={this.togglemodalalterar}>
    <ModalHeader className="teste" toggle={this.togglemodalalterar}>Deletar Banco</ModalHeader>

    ...
    ...

</Modal>

Veja que todos eles tem a mesma repetição do trecho do header, com a mesma marcação em todos eles. Dessa forma podemos deixar esse código repetitivo dentro do componente MyModal:

export default class Mymodal extends Component {
    // código anterior omitido

    render(){
        return(
            <Modal isOpen={this.state.visible} toggle={this._toggleVisibility}>
                <ModalHeader className="teste" toggle={this.togglemodalalterar}>Deletar Banco</ModalHeader>
            </Modal>
        );
    }
}

Suponho que a única mudança que possa ocorrer é o conteúdo da tag que hoje tem o texto "Deletar banco" em todos os três. Se isso for verdade e você precisar de um título que seja dinamico da pra pensar em receber um props com o título:

export default class Mymodal extends Component {
    // código anterior omitido

    render(){
        return(
            <Modal isOpen={this.state.visible} toggle={this._toggleVisibility}>
                <ModalHeader className="teste" toggle={this.togglemodalalterar}>{this.props.modalTitle}</ModalHeader>
            </Modal>
        );
    }
}

Assim fica fácil reutilizar:

exemplo App.js

//import React, { Component } from 'react';
//import Mymodal from './componentes/Mymodal';

render() {

    return (
        <Container>
            <Mymodal visible={false} modalTitle="Deletar Banco">
                // qualquer coisa que queira passar como children
            </Mymodal>

            <Mymodal visible={false} modalTitle="Criar Banco">
                // qualquer coisa que queira passar como children
            </Mymodal>

            <Mymodal visible={false} modalTitle="Qualquer outro titulo pra outro modal">
                // qualquer coisa que queira passar como children
            </Mymodal>
        </Container>
    );
}

Percebeu! O que repete fica no único componente genérico, o que essencialmente muda (é dinâmico) você passa via props (ou mesmo) children. Aplique isso e você consegue.

PS: dicas extras: Imagino que a única coisa que mude são os conteúdos, a marcação de tags em geral vai ficar no componente genérico. Imagino que faz sentido usar o children no corpo do modal body

Agora é com você. Abraço!

Boa tarde Rafael, Agradeço muito a ajuda, entendi bem oque você falou sobre deixar componentes que se repetem fixos no modal.

Porem estou com dificuldades em chamar o Modal queria resolver primeiro esse problema para depois pensar em deixar ele o mais reutilizável e se compensaria tanto trabalho para deixar ele reutilizável.

Meu código ficou assim:

App:

import React, { Component } from 'react';
import './App.css';
import { Container, Button } from 'mdbreact'
import Mymodal from './componentes/Mymodal';




class App extends Component {
  constructor() {
    super();
    this.state = {
      visible: false,
    }
  }

 _toggleVisibility = () => {
   this.setState({
       visible: !this.state.visible      
   });
  console.log('App ' + this.state.visible)
}
  render() {

    return (
      <Container>
        <Mymodal modalTitle="Deletar" visible={this.state.visible}>
        </Mymodal>
           <Button onClick={this._toggleVisibility}>DELETAR</Button>
           <Button>NOVO</Button>
      </Container>
    );
  }
}

export default App;

Modal:

import { Container, Button, Modal, ModalBody, ModalHeader, ModalFooter } from 'mdbreact';
import React, { Component } from 'react';

export default class Mymodal extends Component {
    constructor(props) {
        super(props);
        this.state = {
            visible: false
         }
    }
    _toggleVisibility = () => {
        this.setState({
            visible: !this.state.visible      
        });
        console.log('Modal ' + this.state.visible)
    }
    render(){
        return(
            <Modal isOpen={this.props.visible}>
                <ModalHeader toggle={this._toggleVisibility}>{this.props.modalTitle}</ModalHeader>
                    <ModalBody>

                    </ModalBody>
            </Modal>
        );
    }
}

Rafa oque acontece aqui é um pouco estranho.

Quando eu chamo a função

._toggleVisibility

pelo Button deletar o console log me retorna:

App false

Oque é estranho pois estou invertendo no State o valor, que começa como false:

  this.state = {
            visible: false
         }

para true:

 _toggleVisibility = () => {
   this.setState({
       visible: !this.state.visible      
   });
  console.log('App ' + this.state.visible)
}

E o mais engraçado de tudo isso é que o Modal abre... mesmo retornando False, pensei em desconsiderar esse problema e seguir em frente, porem quando vou no X para fechar o Modal no caso o:

<ModalHeader toggle={this._toggleVisibility}>

ele me retorna:

Modal true

E se clickar novamente retorna:

Modal false

Porem o Modal continua aberto...

Fala Denis,

Então, você tem que lembrar que o setState do react é assíncrono, o que significa dizer que não execução dessa função ...

_toggleVisibility = () => {
        this.setState({
            visible: !this.state.visible      
        });
        console.log('Modal ' + this.state.visible)
    }

... é possível que a chamada do setState seja feita, e antes mesmo de ele terminar a renderização o console.log executar com o estado anterior ao que seria o correto. Esse console log poderia estar no render() pra vc conseguir ver certo (dado que quando o setState faz a alteração de estado um novo render é efetuado).

Sobre o comportamento estranho, aí tem que entra mais a fundo no funcionamento da lib (que não cheguei a mexer antes). Até tentei fazer um teste pesquisando a documentação do react-bootstrap (https://react-bootstrap.github.io/getting-started/introduction). Só que percebi que o código está um pouco diferente (talvez não seja a mesma versão que você está trabalhando). Mas de qualquer forma escrevi um código de exemplo de aplicação react usando a lib do bootstrap conforme a documentação (acima) guia. No projeto de exemplo o código justamente implementa tudo o que discutimos por aqui.

Dê uma olhada no projeto: https://github.com/rafael-rollo/using-react-bootstrap-modal-example

Você pode clonar (ou fazer o download) e na pasta do projeto rodar npm install e npm start pra testar. (se tiver o yarn é a mesma coisa yarn install e yarn start).

Abraço!

Boa tarde Rafa voltei... '-' Mas agora acredito que com uma boa Noticia, Meu código ficou assim: MyModal.js

import React from 'react';
import { Container, Button, Modal, ModalBody, ModalHeader, ModalFooter } from 'mdbreact';

class MyModal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            modal: false
        };
    }
    render() {
        return (
            <Container>
                <Modal isOpen={this.props.modal}>
                    <ModalHeader toggle={this.toggle}>{this.props.ModalHeader}</ModalHeader>
                    <ModalBody>
                        {this.props.children}
                     </ModalBody>
                    <ModalFooter>
                        <Button color={this.props.FFBC} onClick={this.props.FFO}>{this.props.FFBT}</Button>{' '}
                        <Button color={this.props.SFBC} onClick={this.props.toggle}>{this.props.SFBT}</Button>
                    </ModalFooter>
                </Modal>
            </Container>
        );
    }
}
export default MyModal;

Banco.js

export default class Banco extends Component {
    constructor() {
        super();
        this.state = {
            status: 0,
            mensagem: '',
            listbanco: [],
            banco: '',
            modalnew: false,
            modaldel: false,
            listbancosel: []
        }
    }
    togglenew = () => {
        console.log("modalnew")
        this.setState({
            modalnew: !this.state.modalnew
        });
    }
    toggledel = () => {
        console.log("modaldel")
        this.setState({
            modaldel: !this.state.modaldel
        });
    }

Não consegui tirar a repetição dos métodos Toggle e do state, se eu deixar apenas com um, ele abre os 2 modal um atras do outo.

passando as props para o modal:

Modal novo:

<MyModal
             toggle={this.togglenew}
             ButtonName="Deletar"
            ColorButton="danger"
             modal={this.state.modalnew}
              ModalHeader="Cadastrar Banco"
              FFBC="success"
              SFBC="warning"
              FFBT="GRAVAR"
              SFBT="Cancelar"
              FFO={this.save}>

                            <div className="container-fluid">
                                <div className="row">
                                    <div className="col-lg-2">
                                        <label>ID:</label>
                                    </div>
                                    <div className="col-lg-6">
                                        <input onChange={(e) => { this.updateProperty('id', e.target.value) }} type="text"></input>
                                    </div>
                                </div>
                                            ....

Modal del:

 <MyModal
                            toggle={this.toggledel}
                            ButtonName="Deletar"
                            ColorButton="danger"
                            modal={this.state.modaldel}
                            ModalHeader="Deletar Banco"
                            FFBC="danger"
                            SFBC="warning"
                            FFBT="Deletar"
                            SFBT="Cancelar"
                            FFO={this.delete}>

                            <h6>Tem certeza que deseja <b className='red-text'>EXCLUIR</b> os itens selecionados:</h6>
                            <BootstrapTable className="mt-3" pagination striped data={this.state.listbancosel}>
                                <TableHeaderColumn id="tableFinanceiro" isKey dataField='id'>ID</TableHeaderColumn>
                                <TableHeaderColumn dataField='nome'>Nome:</TableHeaderColumn>
                                <TableHeaderColumn dataField='conta'>Conta:</TableHeaderColumn>
                                <TableHeaderColumn dataField='agencia'>Agencia:</TableHeaderColumn>
                                <TableHeaderColumn dataField='ativo'>Ativo:</TableHeaderColumn>
                            </BootstrapTable>
                        </MyModal>
            ....

Caso tenha outra sugestão de melhoria agradeço.

Uma ultima duvida Rafa,

Sobre desempenho, por exemplo eu tenho 3 modais normal que não são reutilizáveis, o navegador ira gastar mais processamento pois ele ira renderizar 3 modal diferente desdo zero, e reutilizando como estou tentando fazer ele apenas gasta processamento renderizando oque mudou ?

Fala Denis, tudo bem ?

No repositório citado na minha resposta anterior tem uma resolução para o problema de ter que ficar com várias funções para toggle (ou varias pra abrir e várias pra fechar) dos modais. Dê uma olhada em como estão escritos os códigos das funções showModal e closeModal. Ela se utiliza de uma propriedade no botão que dispara a abertura para saber qual o modal certo a ser aberto ou fechado.

Sobre a questão de poder ter dois ou três botões a ideia é que seu componente genérico pode também prever essa possibilidade (não precisando criar um outro componente só com essa pequena diferença). Uma possibilidade seria você receber (opcionalmente) uma prop que represente a possibilidade de ter o terceiro botão.

Exemplo ao chamar o componente:

<MyModal ... hasThirdButton={true} thirdButtonTitle="Título do terceiro botão" > ... </MuModal>

e no render do componente MyModal:

render() {
    ...
    <ModalFooter>
        <Button color={this.props.FFBC} onClick={this.props.FFO}>{this.props.FFBT}</Button>
        <Button color={this.props.SFBC} onClick={this.props.toggle}>{this.props.SFBT}</Button>

        {this.props.hasThirdButton &&
            <Button color.. onClick..>
                {this.props.thirdButtonTitle}
            </Button>
        }
    </ModalFooter>
    ...
}

Tente se acostumar com essa forma de pensar. Se a mudança é muito grande na ideia do modal você pode criar outro componente para representá-lo, se é uma pequena possibilidade de mudança , algo opcional, etc, você pode escrever a lógica de renderização no próprio componente existente, desde que receba de fora alguma informação (props) que consiga ser levada em conta pra saber se quem usa o seu componente quer mais um botão, ou se são somente os dois botões como de costume.

Sobre a questão da performance ao descrever vários componentes de modal, ou um único genérico que é renderizado várias vezes, não faz a mínima diferença para o React. De qualquer forma ele quando renderizar, vai precisar renderizar três vezes (no exemplo onde temos tres modais sendo necessários para a app), e ele já tem todas as otimizações necessárias pra garantir que todo render altere apenas o necessário na sua View, evitando trabalho repetitivo.

PS: Dica - tente não usar nomes de variáveis e propriedades como FFBC,FFBT, FFO, SFBT etc. Tente sempre dar nomes bons que deixem claro exatamente o que o valor que ela guarda representa para a lógica e que sejam compreensíveis para os desenvolvedores que podem dar manutenção no projeto futuramente. Evite siglas não conhecidas, abreviações, etc. Variáveis por convenção devem ser declaradas com letras minúsculas, com iniciais de palavras compostas em maiusculo, seguindo o padrão camelCase. Dá pra ver todas as convensões de escrita aqui => https://www.w3schools.com/js/js_conventions.asp

Espero ter ajudado. Abraço!