3
respostas

Um useState para cada campo, ou apenas um useState para um objeto

Olá,

O Vinícios Neves tocou em um assunto que tem sido uma grande dúvida para mim.

Opção 1) criar um state para cada campo:

const[ nome, setNome] = useState('');
const[ email, setemail] = useState('');
const[ endereco, setendereo] = useState('');
const[ complemento, setcomplemento] = useState('');
const[ cep, setcep] = useState('');
const[ senha, setsenha] = useState('');
const[ senhaConfirmada, setsenhaConfirmada] = useState('');

Opção 2) Criar uma classe que representa o usuário. Assim haveria um único state

const[ usuario, setUsuario] = useState( new Usuario());

Minha pergunta. É óbvio que a primeira opção é bem aceita. Mas e a segunda? Se eu optar por ela estou dentro da boas práticas.

Obrigado

3 respostas

Olá, Reinaldo!

Essa é uma dúvida muito comum e a resposta é: depende. Ambas as abordagens têm suas vantagens e desvantagens e a escolha entre uma ou outra depende do contexto do seu projeto.

A abordagem que você escolhe depende muito do contexto e da preferência pessoal. Ambas têm seus méritos.

Opção 1:

  • Granularidade: Cada campo tem seu próprio state, tornando mais simples a atualização e validação individual.
  • Explicitidade: Fica claro quais informações estão sendo gerenciadas.

Opção 2:

  • Organização: Todos os dados relacionados estão agrupados. Isso pode tornar o código mais limpo, especialmente quando se tem muitos campos.
  • Facilidade para operações em lote: Se quiser resetar ou atualizar muitos campos de uma vez, essa abordagem facilita. Dito isso, ambas as opções são aceitáveis.

A segunda opção não é contra as boas práticas, mas é um pouco menos comum em componentes React, já que a granularidade pode oferecer um controle mais fino. Se decidir pela Opção 2, só fique ligado para não mutar o state diretamente; sempre retorne um novo objeto ao atualizar.

Mas se liga, desde a introdução dos Hooks no React (na versão 16.8), a tendência tem sido uma abordagem mais funcional em vez da orientação a objetos com as classes. Mas isso não significa que você não possa (ou não deva) usar classes em suas aplicações React. No entanto, quando se trata do gerenciamento de estado em componentes, os hooks trouxeram uma forma mais concisa e flexível de fazer isso.

Vamos falar sobre o exemplo que você trouxe. Se você estiver pensando em usar uma classe para representar um usuário, você poderia ter algo assim:

class Usuario {
  constructor(nome, email, endereco, complemento, cep, senha, senhaConfirmada) {
    this.nome = nome;
    this.email = email;
    this.endereco = endereco;
    this.complemento = complemento;
    this.cep = cep;
    this.senha = senha;
    this.senhaConfirmada = senhaConfirmada;
  }
}

Se decidir adotar uma abordagem mais funcional com um objeto simples, pode fazer algo assim:

const usuarioInicial = {
  nome: '',
  email: '',
  endereco: '',
  complemento: '',
  cep: '',
  senha: '',
  senhaConfirmada: ''
};

Para o setUsuario com o useState, fica assim:

const [usuario, setUsuario] = useState(usuarioInicial);

Se você quiser atualizar o nome do usuário, por exemplo, evitando mutações diretas (que são uma má prática no React), você pode fazer assim:

setUsuario(prevUsuario => ({ ...prevUsuario, nome: 'Novo Nome' }));

Nesse exemplo, eu usei o spread operator (...) para copiar todas as propriedades do estado anterior (prevUsuario) e depois atualiza a propriedade nome. O prefixo prev vem da palavra previous, que indica que é o usuário "anterior", assim temos acesso a todos o objeto com todos os dados e alteramos somente o que é necessário.

Vinícios, primeiramente obrigado pela resposta. Eu estou utilizando uma abordagem bem diferente.

class Usuario {
        constructor() {
        }

        private _modified: boolean;

        get modified(): boolean {  // Modified controla os botões da tela, tornando-os ativos ou inativos de acordo com este estado
            return this._modified;
        }

        private _nome: string;
        private _email: string;
        private _endereco: string;

        get nome(): string {
            return this._nome;
        }

        set nome(value: string) {
            if(value !== this._nome) {
                this._nome = value;
                performChange("nome");
            }
        }

        get email(): string {
            return this._email;
        }

        set email(value: string) {
            if(value !== this._email) {
                this._email = value;
                performChange("email");
            }
        }

        get endereco(): string {
            return this._endereco;
        }

        set endereco(value: string) {
            if(value !== this._endereco) {
                this._endereco = value;
                performChange("endereco");
            }
        }

        private performChange(columnName: string)
        {
            this._modified = true;

            if(onChange) {
                onchange(this, columnName);
            }
        }

       public onChange: (usuario: Usuario, columnName: string) => void;

        public validate() {

        }

        (...) outras regras de negócio
    }

Como meu objeto NÃO é imutável, precisei criar um hook que força a renderização. fica assim embaixo.

   const[ usuario, setUsuario] = useState<Usuario>( createUsuario() );
   const forceRender = useForceRender();

   function createUsuario() {
       let result = new Usuario();
       result.onChange(usuario: Usuario, columnName: string) {
           //De acordo com a coluna modificada, alguma ação pode ser tomada aqui

          //A alteração da propriedade **modified** pode causar alterações no tela. Exemplo: habilitar o botão gravar

         //A análise do método Validate pode fazer algumas mensagens ser apresentada


        //Por fim, e muito importante
       forceRender(); //força a renderização do componente
   
       }
       
       return result;
   }

Nos inputs não é alterado dado imutável. Porque a execução do forceRender() em onChange é que causa a renderização do componente.


<input value={usuario.nome} onChange={e => usuario.nome = e.target.value}
<input value={usuario.email} onChange={e => usuario.email = e.target.value}

Aqui não estou abordando a facilidade de enviar/receber um objeto via Web Service. E iniciar um objeto novo para iniciar uma nova digitação

Muito obrigado pela atenção. É muito importante ler sua opnião porque não tenho ninguém mais para recorrer.

Para limpar a tela para iniciar um nova inclusão basta um linha.

     setUsuario(createUsuario());

Para chamar o Web Service para uma gravação basta informar o objeto.

CUsuarioService.wsInsert(Usuario, (e) => { ... }

Para receber os dados do Web Service basta duas linhas

        CUsuarioService.wsGetCadastro(numero, (e) => {
            if (e.isSuccess && e.data) {
                let usuario = Object.assign(new Usuario(), e.data.usuario);
                setUsuario(usuario);
            }
        });

Mas a verdadeira vantagem é poder encapsular as regras de negócio em uma classe. Principalmente numa classe mais complexa com mais validações, calculos, etc. E com a possibilidade maior de reutilização.

Agradeço a atenção, e aguardo comentários.