Java para Web

Aula 13 : CRUD

Aula 13 - CRUD

Exemplo de criação de um CRUD de um recurso único usando o React

Exemplos de aula

Criando o CRUD

Modelo de CRUD React

Para o exemplo que seguiremos a seguir, vamos um backend contendo o recursopokemons, com os atributos _id, nome, numeroe url. Os métodos definidos para esse recurso são GET,POST, PUT e DELETE.

Para o frontend, usaremos como base 3 componentes:

  • FormPokemon, que renderiza o formulário para inserir e atualizar
  • ListaPokemon, que renderiza a listagem de pokemons
  • Pokemon, que renderiza o formulário e listagem

Iniciando CRUD React

Vamos iniciar a criação do nosso front-end gerando nosso pacote react através do comando:

npx create-react-app nome-projeto

Em seguida, vamos alterar o arquivo App.js para renderizar nosso primeiro componente:

js App.js
import './App.css';
import Pokemon from './components/Pokemon'

function App() {
    return (
        <Pokemon></Pokemon>
    )
}

export default App;

Todos os nossos componentes citados anteriormente serão criados dentro da pastacomponents

Dentro do arquivo components/Pokemon.js, vamos criar a estrutura geral da página:

js Pokemon.js
import React from 'react'
import Axios from 'axios'
import ListaPokemon from './ListaPokemon'
import FormularioPokemon from './FormularioPokemon'

export default class Pokemon extends React.Component{

    constructor(props){
        super(props)

        this.API_URL = "http://localhost:8080/pokemons"

        this.state = {
            "pokemons": [],
        }
    }

    render(){
        return <main>
            <h2>Formulario</h2>
            <section>
                <FormularioPokemon
                    pokemons={this.state.pokemons}>
                </FormularioPokemon>
            </section>
            <h2>Lista</h2>
            <section>
                <ListaPokemon>
                </ListaPokemon>
            </section>
        </main>
    }

}

Repare que temos, dentro da classe Pokemon a inicialização do estado que vai armazenar uma lista de pokemons. Isso garante que é este componente que fará o gerenciamento dos dados, repassando-os aos filhos FormularioPokemon e ListaPokemon. Neste caso, ListaPokemon já recebe o this.state.pokemons para renderizar a lista de itens posteriormente.

Em seguida, temos a definição do componente components/FormularioPokemon.js:

js FormularioPokemon.js
import React from 'react'

export default class FormularioPokemon extends React.Component{

    constructor(props){
        super(props)
        this.state = {
            "nome": "",
            "numero": 0
        }
    }

    handleInput = (event) => {
        this.setState({
            [event.target.id]: event.target.value
        })
    }

    render(){
        return(
            <form>
                <input type="text" id="nome" onChange={this.handleInput} value={this.state.nome}></input>
                <input type="number" id="numero" onChange={this.handleInput} value={this.state.numero}></input>
                <button type="button">Inserir</button>
            </form>
        )
    }
}

Repare que aqui já encapsulamos o estado das nossas entradas dentro do estado do componente. Dessa forma, os inputs usarão o método handleInput para alterar o estado a cada mudança no estado interno do input

E por fim, do componente components/ListaPokemon.js:

js ListaPokemon.js
import React from 'react'
import FormularioPokemon from './FormularioPokemon'

export default class ListaPokemon extends React.Component{

    render(){
        var html = ""
        var lista = this.props.pokemons
        html = lista.map((item) => {
            return <li key={item._id}>
                <div>{item.nome}</div>
            </li>
        })

        return <ul>
            {html}
        </ul>
    }
}

Aqui fazemos a iteração da lista recebida pelo props e realizamos sua impressão. Mais adiante, também adicionaremos dois botões, para deletar e selecionar itens dessa lista.

Listando CRUD React

Nosso frontend já está preparado para renderizar os itens do this.state.pokemons, no entanto, ainda não temos nenhum dado. Vamos criar uma nova função que atualiza o estado a partir do backend, usando o Axios:

js Pokemon.js
export default class Pokemon extends React.Component{
    //...
    getAllPokemons = () => {
        var requisicao = Axios.get(this.API_URL)
        requisicao.then((resposta) => {
            this.setState({
                "pokemons": resposta.data
            })
        })
    }
    //...
}

Em seguida, vamos chamar esse método por meio de um dos métodos do ciclo de vida do React: ocomponentDidMount(). Esse método é chamado apenas quando o componente é montado no DOM, fazendo dele um momento ideal para atualizar os dados da lista.

js Pokemon.js
export default class Pokemon extends React.Component{
    //...

    componentDidMount = () => {
        this.getAllPokemons()
    }

    getAllPokemons = () => {
        var requisicao = Axios.get(this.API_URL)
        requisicao.then((resposta) => {
            this.setState({
                "pokemons": resposta.data
            })
        })
    }
    //...
}

Chamar o método getAllPokemons a partir do constructor, apesar de funcionar, não é recomendado devido a natureza do ciclo de vida React. Em alguns casos, é possível que o construtar seja chamado mais de uma vez, em momentos diferentes, podendo resultado em lentidão extra ou mesmo inconsistências no componente.

Inserindo CRUD React

Após a listagem de dados, vamos permitir que o formulário da página chame um novo método que criaremos no Pokemon.js, que resultará na inserção de um novo item no backend. Primeiro, vamos criar o método principal:

js Pokemon.js
export default class Pokemon extends React.Component{
    //...
    addPokemon = (pokemon) => {
        var requisicao = Axios.post(this.API_URL, pokemon)
        requisicao.then((resposta) => {
            if(resposta.status == 200){
                this.getAllPokemons()
            }
        })
    }
    //...
}

A verificação de esposta.status == 200 aqui serve para atualizar toda a lista caso haja sucesso na inserção do novo item.

Vamos agora dar acesso à este método ao componente filho FormularioPokemon, para que ele possa chamá-lo:

js FormularioPokemon.js
export default class Pokemon extends React.Component{
    //...
    render(){
        return <main>
            <h2>Formulario</h2>
            <section>
                <FormularioPokemon
                    pokemons={this.state.pokemons}
                    add={this.addPokemon}>
                </FormularioPokemon>
            </section>
            <h2>Lista</h2>
            <section>
                <ListaPokemon>
                </ListaPokemon>
            </section>
        </main>
    }
    //...
}

Por fim, vamos atualizar nosso FormularioPokemon.js, adicionando um botão e criando um novo método:

js FormularioPokemon.js
export default class FormularioPokemon extends React.Component{

//...

    setPokemon = () => {
        this.props.add({
            "nome": this.state.nome,
            "numero": this.state.numero
        })

        this.setState({
            "nome": "",
            "numero": 0
        })
    }

    render(){
        return(
            <form>
                <input type="text" id="nome" onChange={this.handleInput} value={this.state.nome}></input>
                <input type="number" id="numero" onChange={this.handleInput} value={this.state.numero}></input>
                <button type="button" onClick={this.setPokemon}>Inserir</button>
            </form>
        )
    }
}

Nesse caso, temos um novo método interno para FormularioPokemon.js, que além de chamar o método pai this.props.add, também limpa o estado das entradas logo em seguida. Esse método interno é chamado sempre que clicamos no botão Inserir (eventoonChange).

Deletando CRUD React

Agora que já somos capazes de inserir e listar itens, vamos implementar também a deleção. Para isso, necessitaremos de um novo método e de um botão ao lado de cada item da nossa lista.

Vamos começar com o método principal, que será implementado em Pokemon.js:

js Pokemon.js
export default class Pokemon extends React.Component{
                        //...

    deletePokemon = (pokemonId) => {
        var requisicao = Axios.delete(this.API_URL + '/' + pokemonId)
        requisicao.then((resposta) => {
            if(resposta.status == 200){
                this.getAllPokemons()
            }
        })
    }

    render(){
        return <main>
            <h2>Formulario</h2>
            <section>
                <FormularioPokemon
                    pokemons={this.state.pokemons}
                    add={this.addPokemon}>
                </FormularioPokemon>
            </section>
            <h2>Lista</h2>
            <section>
                <ListaPokemon
                delete={this.deletePokemon}>>
                </ListaPokemon>
            </section>
        </main>
    }
    //...
}

Novamente criamos um método e repassamos seu ponteiro através das propriedades do objeto, como haviamos feito anteriormente com addPokemon. Repare que, ao contrário deaddPokemon, nosso método deletePokemon recebe um id. Esseid será repassado pela ListaPokemon, pois é este componente que conhece qual item será excluido e vai chamar que o método principal. Observe ainda que a construção da URL é diferente em ambos os casos, já que a toda de DELETE precisa doid.

Vamos agora atualizar nosso ListaPokemon.js com um novo botão:

js
export default class ListaPokemon extends React.Component{

render(){
    var html = ""
    var lista = this.props.pokemons
    html = lista.map((item) => {
        return <li key={item._id}>
            <div>{item.nome}</div>
            <div>
                <button type="button" onClick={() => {this.props.delete(item._id)}}>Deletar</button>
            </div>
        </li>
    })

    return <ul>
        {html}
    </ul>
}
}

Observe atentamente que neste caso não criamos nenhum método interno para o componente filhoListaPokemon. O uso deonClick={() =&gt; {this.props.delete(item._id)}} foi suficiente já que, na prática, estamos criando uma arrow funcion diretamente no valor do atributo onChange

Uma outra forma de passar um parâmetro fixo para um método é criando um sub-componente. Dessa forma, evitamos criar uma nova função para cada item da lista.

Atualizando CRUD React

Para editar os itens, podemos utilizar de várias estratégias diferentes. Neste exemplo, iremos alterar o componente FormPokemon para permitir que, além de inserir, ele possa também alterar itens da nossa lista. Para isso, vamos implementar uma forma de selecionar e identificar itens selecionados, para que nosso componente saiba exatamente quando inserir e quando deve editar um item previamente inserido.

Este método exigirá que façamos alterações nos três componentes.

Vamos iniciar com a alteração do componente principal Pokemon.js:

js Pokemon.js
export default class Pokemon extends React.Component{

constructor(props){
    super(props)

    this.API_URL = "http://localhost:8080/pokemons"

    this.state = {
        "pokemons": [],
        "selecionado": null
    }
}

//...

selectPokemon = (pokemon) => {
    if(this.state.selecionado == pokemon){
        this.setState({
            'selecionado': null
        })
    } else {
        this.setState({
            'selecionado': pokemon
        })
    }
}

render(){
    return <main>
        <h2>Formulario</h2>
        <section>
            <FormularioPokemon
                pokemons={this.state.pokemons}
                add={this.addPokemon}>
            </FormularioPokemon>
        </section>
        <h2>Lista</h2>
        <section>
            <ListaPokemon
            delete={this.deletePokemon}
            select={this.selectPokemon}>
            </ListaPokemon>
        </section>
    </main>
}
//...
}

Nesta primeira parte, realizamos 3 adições no nosso código:

  • Inserimos um novo atributo no state chamado selecionado. Essa variável irá controlar qual dos elementos está selecionado (e será nulo caso não haja seleção)
  • Um método que permite selecionar um elemento, gravando-o no estado. Caso o elemento selecionado já esteja selecionado, vamos atribuir o valor null, deselecionando o item.
  • Passamos por props o método anterior para que a nossa ListaPokemonpossa controlar a seleção de itens

Vamos aproveitar e adicionar também um método para atualizar um item no backend, repassando aprop para o FormularioPokemon:

js FormularioPokemon.js
export default class Pokemon extends React.Component{
    //...

    putPokemon = (pokemon) => {
        if(this.state.selecionado){
            var requisicao = Axios.put(this.API_URL + '/' + this.state.selecionado._id, pokemon)
            requisicao.then((resposta) => {
                console.log(resposta)
                if(resposta.status == 200){
                    this.setState({"selecionado": null})
                    this.getAllPokemons()
                }
            })
        }
    }

    render(){
        return <main>
            <h2>Formulario</h2>
            <section>
                <FormularioPokemon
                    pokemons={this.state.pokemons}
                    add={this.addPokemon}
                    put={this.putPokemon}>
                </FormularioPokemon>
            </section>
            <h2>Lista</h2>
            <section>
                <ListaPokemon
                delete={this.deletePokemon}
                select={this.selectPokemon}>
                </ListaPokemon>
            </section>
        </main>
    }
    //...
}

Basicamente essa função utiliza o _id que está armazenado emthis.state.selecionado para realizar a atualização, mas usa um objeto contendo os dados atualizados que é recebido por parâmetro. Também não podemos esquecer de, após a atualização, resetar o valor do item selecionado.

Agora, vamos alterar nosso ListaPokemon.js para criar um botão de seleção para cada item:

js ListaPokemon.js
export default class ListaPokemon extends React.Component{

    render(){
        var html = ""
        var lista = this.props.pokemons
        html = lista.map((item) => {
            return <li key={item._id}>
                <div>{item.nome}</div>
                <div>
                    <button type="button" onClick={() => {this.props.delete(item._id)}}>Deletar</button>
                    <button type="button" onClick={() => {this.props.select(item)}}>Editar</button>
                </div>
            </li>
        })

        return <ul>
            {html}
        </ul>
    }
}

Repare que assim como anteriormente, criamos um arrow function para chamar o método paithis.props.select, porém agora passando o objeto inteiro (ao invés apenas do_id) de volta para o componente principal.

Por fim, vamos realizar as alterações no FormPokemon.js para que ele possa

js FormPokemon
export default class FormularioPokemon extends React.Component{

constructor(props){
    super(props)
    
    (this.props.selecionado){
        this.state = {
            "nome": this.props.selecionado.nome,
            "numero": this.props.selecionado.numero
        }
    } else {
        this.state = {
            "nome": "",
            "numero": 0
        }
    }
}

setPokemon = () => {
    if(this.props.selecionado){
        this.props.put({
            "nome": this.state.nome,
            "numero": this.state.numero
        })
    } else {
        this.props.add({
            "nome": this.state.nome,
            "numero": this.state.numero
        })
    }

    this.setState({
        "nome": "",
        "numero": 0
    })
}
                    
    render(){

        var selecionado = this.props.selecionado ? this.props.selecionado._id : null

        return(
            <form>
                <input type="text" id="nome" onChange={this.handleInput} value={this.state.nome}></input>
                <input type="number" id="numero" onChange={this.handleInput} value={this.state.numero}></input>
                <button type="button" onClick={this.setPokemon}>Inserir</button>
            </form>
            {selecionado}
        )
    }
}

Neste componente, pra finalizar a função de edição, fizemos a seguintes alterações:

  • Redefinimos o construtor para inicializar o estado das entradas de acordo com o item selecionado (e se não houver, inicialize com valores padrão)
  • Alteramos a função setPokemon para chamar this.props.put caso haja algum pokemon selecionado
  • Na renderização, testamos novamente se há algum pokémon selecionado e mostramos o _id dele. Essa alteração é apenas estética, para sabermos se há algum item selecionado

Após todas essas alterações, você pode notar que é possível editar, inserir e deletar os itens. No entanto, o formulário não atualiza automaticamente quando um novo item é selecionado. Isso acontece pois definimos os valores das entradas do formulário em seu construtor. Como o componente não é recriado devido ao fato de não saber quando o houve uma nova seleção, vamos usar uma espécie de truque para forçar a recriação do componente: vamos inserir o atributo key no formulário

js Pokemon.js
export default class Pokemon extends React.Component{
    render(){
        return <main>
            <h2>Formulario</h2>
            <section>
                <FormularioPokemon
                    pokemons={this.state.pokemons}
                    add={this.addPokemon}
                    put={this.putPokemon}
                    key={this.state.selected._id}>
                </FormularioPokemon>
            </section>
            <h2>Lista</h2>
            <section>
                <ListaPokemon
                delete={this.deletePokemon}
                select={this.selectPokemon}>
                </ListaPokemon>
            </section>
        </main>
    }
    //...
}

Agora o componente formulário será recriado por completo a cada atualização da chave

Apesar de haver soluções mais performáticas aqui que abrimos mão pela facilidade na hora da implementação

E enfim temos o nosso CRUD completo para o recurso!