4
respostas

CRUD, boas práticas

Eu estou quebrando a regra de imutabilidade. Por isto resolvi fazer esta pergunta. Vou fazer um praralelo de duas formas diferentes de fazer CRUD. Criei dois modelos o mais básico possível

Modelo 1

Acredito que este modelo é o mais utilizado, e diria considerado correto. Cada campo é um state separado. Então pra tudo deve-se trabalhar campo a campo. Como limpar o formulário, transmitir Web Service, receber Web Service. Tudo campo a campo.

Acho que a coisa complica ainda mais com relação a regras de negôcio. A forma de fazer validações, cáculos matemáticos. É um retrocesso na POO.

function App() {
  const [codigo, setCodigo] = useState('');
  const [descricao, setDescricao] = useState('');
  const [un, setUN] = useState('');

  return (
    <div className="App" style={{ display: 'flex', flexDirection: 'column' }}>
      <div>
        <label>Código</label>
        <input type='text' value={codigo} onChange={(e) => setCodigo( e.target.value)} />
      </div>
      <div>
        <label>Descrição</label>
        <input type='text' value={descricao} onChange={(e) => setDescricao( e.target.value)} />
      </div>
      <div>
        <label>UN</label>
        <input type='text' value={un} onChange={(e) => setUN( e.target.value)} />
      </div>

      <div style={{marginTop: "10px"}}>
        <label>Teste</label>
        <input type='text' value={`${codigo} - ${descricao} - ${un}`} onChange={(e) => e.target.value} />
      </div>
    </div>
  );
}

Modelo 2

É o modelo que estou utilizado. Mas acredito estar ferindo princípios do React. Eu crio uma classe para representar os dados e encapsular as operações como validações e cálculos. Como as propriedades não são states, preciso de um mecanismo que força a renderização. No exemplo abaixo uma instância da classe Produto mantém os dados, e o state "forceRender" força a renderização sempre que uma propriedade munda.

A ideia é ter na classe Produto não só com as propriedade inerentes a ela, ma as ações como validações, etc... É a ideia da POO.

Por exemplo para enviar ou receber um WebService, é simplesmente lidar com uma instância da classe. Não preciso tratar campo a campo

class Produto {
    private _codigo: string = '';
    private _descricao: string = '';
    private _un: string = '';

    onchange?: () => void;

    performeChange() {
        if (this.onchange) {
            this.onchange();
        }
    }

    get codigo() {
        return this._codigo;
    }

    set codigo(value: string) {
        this._codigo = value;
        this.performeChange();
    }

    get descricao() {
        return this._descricao;
    }

    set descricao(value: string) {
        this._descricao = value;
        this.performeChange();
    }

    get un() {
        return this._un;
    }

    set un(value: string) {
        this._un = value;
        this.performeChange();
    }
    
    validate(): string {
        if(!codigo || !descricao) {
           return 'Informe o código e a desrição.';
        }
    }
}


function App() {
    const [produto, setProduto] = useState(createProduto());
    const [forceRender, setforceRender] = useState(0);

    function createProduto() {
        const result = new Produto();
        result.onchange = () => setforceRender(prev => prev + 1);
        return result;
    }

    return (
        <div className="App" style={{ display: 'flex', flexDirection: 'column' }} data-force-render={forceRender}>
            <div>
                <label>Código</label>
                <input type='text' value={produto.codigo} onChange={(e) => produto.codigo = e.target.value} />
            </div>
            <div>
                <label>Descrição</label>
                <input type='text' value={produto.descricao} onChange={(e) => produto.descricao=e.target.value} />
            </div>
            <div>
                <label>UN</label>
                <input type='text' value={produto.un} onChange={(e) => produto.un = e.target.value} />
            </div>

            <div style={{ marginTop: "10px" }}>
                <label>Teste</label>
                <input type='text' value={`Objeto: ${produto.codigo} - ${produto.descricao} - ${produto.un}`} onChange={(e) => e.target.value} />
            </div>
        </div>
    );
}

Sugestões

Preciso do comentário de alguém mais experiente do que eu. Posso continuar utilizando o modelo 2? Ou seria melhor abandoná-lo e passar a utilizar o modelo 1?

4 respostas

Oi Reinaldo, tudo bem?

Sua pergunta é muito relevante e é uma dúvida comum entre desenvolvedores que estão migrando de uma abordagem orientada a objetos para o React.

Vamos lá, o React segue o paradigma de programação funcional, que difere bastante da programação orientada a objetos. No React, a imutabilidade é um princípio fundamental, e é por isso que o estado é tratado da maneira que é no Modelo 1 que você apresentou. Cada vez que um estado é alterado, o componente é renderizado novamente. Isso é o que torna o React tão eficiente.

No Modelo 2, você está tentando trazer conceitos de orientação a objetos para o React, o que pode levar a alguns problemas. Por exemplo, a maneira como você está forçando a renderização não é a maneira recomendada de lidar com atualizações de estado no React. Além disso, a abordagem orientada a objetos pode tornar o código mais difícil de testar e pode levar a efeitos colaterais indesejados.

No entanto, isso não significa que você não possa usar classes e métodos em seu código React. A chave é entender quando e onde eles são apropriados. Por exemplo, você pode criar uma classe Produto fora do seu componente e usar seus métodos para lidar com a lógica de negócios, como validação. Mas a representação do estado do produto e a manipulação do estado devem ser feitas dentro do componente usando o useState.

Espero que isso esclareça suas dúvidas. Lembre-se, a chave é entender os princípios do React e usá-los a seu favor.

Um abraço e bons estudos.

Olá Lorena,

Compreendi sua explicação e vou rever minha forma de trabalhar. Mas surgiu uma dúvida. No modelo de classe que fiz faltou a propriedade "modified". Eu uso esta propriedade para controlar o estado dos Botões.

Exemplo: Modified === false => Botão Gravar desabilitado, Botão Cancelar desabilitado, Botão Excluir habilitado, Botão Voltar habilitado Modified === true => Botão Gravar habilitado, Botão Cancelar habilitado, Botão Excluir desabilitado, Botão Voltar desabilitado

Modified sempre começa em false, e fica true quando qualquer atributo seja moficado (ex. codigo, descricao, etc.) Usando o modelo 1 como posso fazer isto?

Muito Obrigado,

Outra questão é a gestão de snackbar do MUI para apresentar erros de validação. Parece-me que uma abordagem POO facilitaria o processo.

Se você puder me indicar algo sobre a gerência do "modified" da mensagem anterior e do snackbakbar desta mensagem, no modelo 1 agradeço.

Desculpe-me se estou abusando, mas quero seguir as boas práticas e aqui é meu melhor recurso para buscar informação.

Obrigado,

Oi Reinaldo,

Aqui está um exemplo simplificado de como você pode implementar o controle de botões no Modelo 1:

// Defina um estado para rastrear as modificações
const [isModified, setIsModified] = useState(false);

// Manipuladores de evento para campos
const handleCodigoChange = (e) => {
  setCodigo(e.target.value);
  setIsModified(true); // Marque como modificado
};

// Repita o mesmo padrão para outros campos

// No JSX, use o estado isModified para controlar a habilitação dos botões
<button disabled={!isModified}>Salvar</button>
<button disabled={!isModified}>Cancelar</button>
<button disabled={isModified}>Excluir</button>
<button disabled={isModified}>Voltar</button>

Respostas às Suas Perguntas Específicas

  1. Controle de Modificações: No Modelo 1, você pode controlar as modificações dos campos usando um estado adicional, como mostrado no exemplo acima. Cada vez que um campo é modificado, você define isModified como true. Isso permitirá que você controle o estado dos botões com base nas modificações.

  2. Gestão de Snackbar: Para a gestão de Snackbar no Modelo 1, você pode usar bibliotecas como o Material-UI para criar Snackbars em resposta a erros de validação. Essa abordagem é perfeitamente viável e alinhada com as práticas recomendadas.

Aqui está um exemplo simplificado de como mostrar um Snackbar em caso de erro de validação no Modelo 1:

// Defina um estado para controlar o Snackbar
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');

// Função para mostrar o Snackbar
const showSnackbar = (message) => {
  setSnackbarMessage(message);
  setSnackbarOpen(true);
};

// Em algum lugar de seu código, ao encontrar um erro de validação
if (erroDeValidacao) {
  showSnackbar('Erro de validação: informe o código e a descrição.');
}

// Componente Snackbar do Material-UI
<Snackbar
  open={snackbarOpen}
  autoHideDuration={6000}
  onClose={() => setSnackbarOpen(false)}
  message={snackbarMessage}
/>

Indico a leitura da documentação também.

Em suma, tanto o Modelo 1 quanto o Modelo 2 têm suas vantagens e desvantagens. O Modelo 1 segue as práticas recomendadas do React, é mais simples de entender, testar e depurar, e aproveita a eficiência do React em relação à imutabilidade.

No entanto, isso não significa que você não possa usar conceitos orientados a objetos em sua aplicação React. Você pode criar classes e métodos para lidar com a lógica de negócios, validações e cálculos, desde que a manipulação de estado seja feita de acordo com as práticas do React.

No seu caso, o controle de modificações e a gestão de Snackbars podem ser implementados efetivamente no Modelo 1, como mostrado nos exemplos acima. Portanto, minha recomendação é que você continue aprimorando suas habilidades no Modelo 1, seguindo as práticas recomendadas do React, para criar aplicativos mais eficientes e fáceis de manter.

Espero que esta resposta tenha esclarecido suas dúvidas e te ajudado a tomar decisões informadas.

Um abraço e bons estudos.