Consumindo API GraphQL em React.jscom Apollo 클라이언트

Essaéa segunda eúltima parte da série de como montar uma aplicaão completa em GraphQL.Aqui vamos construiruma aplica ão frontend para interagircom o backend criado no primeiro 강좌.
Para seguir com esse tutorial,énecessário noçes de GraphQL,além de React.js.Para se는 com GraphQL, veja o seguinte artigo를 잘 알고 있습니다.
📝 GraphQL: O que é e como usar
Se quiser ver o primeiro tutorial de como montar uma API GraphQL,veja o seguinte artigo:
📝 Montando API GraphQL em Node.js com Apollo e MongoDB
Vocêpode acompanhar o tutorial passo-a-passo ou clonar o repositório completo do GitHub.
Além disso eu disponilizei uma versão online(sem mutations para que não haja mudança nos dados online)a título de exemplo do resultado final da API.
항목 링크:

  • Código no GitHub:github.com/emerson-pereira/frutas

  • Versão online da API:graphql-frutas.herokuapp.com

  • Vers ão 온라인 애플리케이션 React: codesandbox.io/s/graphql-frutas-4isf8
  • 제안


    제안 사이트 sobre frutas onde podemos gerenciar os dados fazendo as opera öes CRUD.O 현장 seráfeito em 반응.js e o servidor em 노드.js.Nesse 자습서 desenvolveremos o frontend em React.js.

    O 스택


    프런트엔드 없음, teremos:
  • 프레임워크React.js aplicaão단

  • Apollo Client for React.jspara consumir dados da API GraphQL em React.js
  • Iniciando 응용 프로그램 반응회사 명


    Aqui devemos continuar dentro da pastafruitsde onde começamos no tutorial front.Dentro dela, o seguinte comando 단락 실행iniciar um projeto react:
    npx create-react-app frontend
    
    Quando terminado o processo,uma pastafrontendterásido criada com a aplicaão inicial React.js:
    📦fruits
    ┣ 📂backend
    ┣ 📂frontend
    ┃ ┣ …
    
    Abra um terminal de comandos e navegue para a pastafruits/frontend.수행자 기능 확인:
    npm start
    
    Deveráabrir a tela inicial gerada com create react app na porta 3000:
    http://localhost:3000

    Aplicaão iniciada!
    Antes de come çarmos,ferramenta create react 응용 프로그램cria alguns arquivos que n ão Neces á rios aqui,como arquivos de teste configura ço de service worker.Apague todos esses arquivos,atéficar com a seguinte estrutura:
    📂frontend
     ┣ 📂public
     ┃ ┣ 📜favicon.ico
     ┃ ┣ 📜index.html
     ┣ 📂src
     ┃ ┣ 📜App.css
     ┃ ┣ 📜App.js
     ┃ ┣ 📜index.css
     ┃ ┣ 📜index.js
     ┣ 📜.gitignore
     ┣ 📜package.json
     ┗ 📜README.md
    
    Agora vamos“limpar”alguns arquivos removendo algumas chamadas e demais coisas desnecesárias.
    Começando na 파스타public, abraindex.htmle deixe dessa maneira:
    카미니오: frontend/public/index.html
    <!DOCTYPE html>
    <html lang="pt-BR">
      <head>
        <meta charset="utf-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />
        <meta
          name="description"
          content="Um app sobre informações nutricionais de frutas."
        />
        <title>Frutas</title>
      </head>
      <body>
        <noscript>
          You need to enable JavaScript to run this app.
        </noscript>
        <div id="root"></div>
      </body>
    </html>
    
    Agora,vamos adicionar os estilos que serão usado nesta aplicaão.Na pastasrc,substitua os conteúdos deindex.csseApp.csscom os seguintes conteúdos:
    카미니오: frontend/src/index.css
    body {
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
        "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
        "Droid Sans", "Helvetica Neue", sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    
    input,
    button {
      padding: 10px;
      font-size: calc(10px + 1vmin);
    }
    
    button:hover {
      cursor: pointer;
    }
    
    ul {
      list-style: none;
      margin: 20px 0;
      padding: 0;
    }
    
    li {
      display: flex;
      justify-content: space-between;
      align-items: baseline;
      padding: 10px;
      margin: 10px;
    }
    
    카미니오: frontend/src/App.css
    .App {
      text-align: center;
    }
    
    .App-header {
      background-color: #282c34;
      color: white;
      position: absolute;
      top: 10%;
      right: 0;
      width: 100vw;
    }
    .App-header h1 {
      margin: 0;
      padding: 20px;
    }
    
    .App-body {
      background-color: #282c34;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-size: calc(10px + 2vmin);
      color: white;
    }
    
    .App-viewbox {
      position: relative;
    }
    
    .App-close-btn {
      position: absolute;
      top: -100px;
      right: -100px;
    }
    
    .App-close-btn button {
      background: none;
      border: 0;
      color: white;
      font-size: calc(10px + 2vmin);
    }
    
    .App-btn {
      max-width: 120px;
      width: 100%;
    }
    
    .App-btn.secondary {
      background: transparent;
      border: 2px solid white;
      color: white;
    }
    
    .App-item-actions {
      margin-left: 40px;
    }
    
    .App-item-actions a {
      margin: 0 10px;
      background: none;
      text-decoration: none;
    }
    
    .App-item-actions a:hover {
      cursor: pointer;
    }
    
    Estilos adicionados.Agora vamos a 파스타index.js dentro desrc e certificar que o arquivo est á como a seguir:
    카미니오: frontend/src/index.js
    import React from "react"
    import ReactDOM from "react-dom"
    import "./index.css"
    import App from "./App"
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById("root")
    )
    
    E agora,oúltimo arquivo a ser checado antes de começarmos com a aplicaço.Deixesrc/App.jsda seguinte maneira:
    카미니오: frontend/src/App.js
    import React from "react"
    import "./App.css"
    
    function App() {
      return (
        <div className="App">
          <div className="App-header">
            <h1>Frutas</h1>
          </div>
          <div className="App-body"></div>
        </div>
      )
    }
    
    export default App
    
    Agora salve tudo e abra no navegador,certifique que não háerros no console.Deveráaparecer dessa forma:

    Assim,concluímos a configuraço inicial do projeto,vamos agora ao próximo passo.

    구성 및 회전


    Para facilitar a navegaço entre rotas,vamos usar a bibliotécaReact router.Instale-a com o comando:
    npm i react-router-dom
    
    Dentro da pastasrccrie um arquivo chamadoroutes.jse inicie as rotas dessa maneira:
    카미니오: frontend/src/routes.js
    import React from "react"
    import {
      BrowserRouter as Router,
      Switch,
      Route,
    } from "react-router-dom"
    
    import Fruits from "./components/Fruits"
    
    const Routes = () => (
      <Router>
        <Switch>
          <Route exact path="/">
            <Fruits />
          </Route>
        </Switch>
      </Router>
    )
    
    export default Routes
    
    A propriedadepathindica em qual caminho da aplicaão aquele componente seráexibido,no caso deFruits,este seráexibido na home da aplicaão.
    Agora,vamos criar o componenteFruits.jsque estásendo chamando no arquivo de rotas.Esse componente mostraráuma lista de frutas assim como as aões de exibir,editar e excluir de cada fruta.
    Dentro desrc, crie uma 파스타components.Dentro desta,crie o componente de frutas:
    카미니오: frontend/src/components/Fruits.js
    import React from "react"
    import { Link } from "react-router-dom"
    
    const FruitsList = () => {
      return (
        <>
          <ul>
            <li>
              <span>Banana</span>
              <div className="App-item-actions">
                <Link>
                  <span role="img" aria-label="visualizar">
                    👀
                  </span>
                </Link>
                <Link>
                  <span role="img" aria-label="editar">
                    ✏️
                  </span>
                </Link>
                <Link>
                  <span role="img" aria-label="excluir"></span>
                </Link>
              </div>
            </li>
          </ul>
    
          <p>
            <Link>
              <button>Nova Fruta</button>
            </Link>
          </p>
        </>
      )
    }
    
    export default FruitsList
    
    Por enquanto adicionamos uma lista com apenas uma fruta.
    Também criamosLinkao redor dos botões,mas não apontamos para nenhuma rota,nesse momento.Faremos isso Mai는 친구입니다.
    Agora,váatéApp.jse inclua a rota criada:
    카미니오: frontend/src/App.js
    import React from "react"
    import "./App.css"
    import Routes from "./routes"
    function App() {
      return (
        <div className="App">
          <div className="App-header">
            <h1>Frutas</h1>
          </div>
          <div className="App-body">
            <Routes /> </div>
        </div>
      )
    }
    
    export default App
    
    인증서 a lista de frutas criada aparece na tela inicial da aplica ão.
    Agora,o próximo passo:

    ConectandoáAPI GraphQL com Apollo


    Vamos come çar instalando는 deped ências para usar apollo 고객입니다.
    주: Aqui estamos usando apollo 고객 navers ão 3.
    npm i @apollo/client graphql
    

  • @ 아폴로/고객: Pacote apollo com o Necessario para usar apollo 고객

  • graphql: Pacote OFFICIAL do graphql com a l ógica para parsear 조회
  • Agora,efetuamos a conexão usando a URL da API no backend.Como estamos desenvolvendo tudo localmente,vamos fornecer a URL local do backend que serve na porta 4000.
    카미니오: frontend/src/App.js
    import React from "react"
    import { ApolloProvider, ApolloClient, InMemoryCache,} from "@apollo/client"import "./App.css"
    import Routes from "./routes"
    
    const client = new ApolloClient({ uri: "http://localhost:4000", cache: new InMemoryCache(),})
    function App() {
      return (
        <ApolloProvider client={client}> <div className="App">
            <div className="App-header">
              <h1>Frutas</h1>
            </div>
            <div className="App-body">
              <Routes />
            </div>
          </div>
        </ApolloProvider> )
    }
    
    export default App
    
    Agora vamos voltar ao componenteFruits.jse popular o componente com dados vindos da API usando Apollo client.
    카미니오: frontend/src/components/Fruits.js
    import React from "react"
    import { gql, useQuery } from "@apollo/client"import { Link } from "react-router-dom"
    
    export const GET_FRUITS = gql` { fruits { id name } }`
    const FruitsList = () => {
      const { loading, error, data } = useQuery(GET_FRUITS) if (loading) return <p>Loading...</p> if (error) return <p>Error :(</p>
      return (
        <>
          <ul>
            {data.fruits && data.fruits.map(({ name, id }) => ( <li key={id}> <span>{name}</span> <div className="App-item-actions"> <Link to={`/fruit/${id}`}> <span role="img" aria-label="visualizar"> 👀 </span> </Link> <Link to={`/editFruit/${id}`}> <span role="img" aria-label="editar"> ✏️ </span> </Link> <Link to={`/deleteFruit/${id}`}> <span role="img" aria-label="excluir"></span> </Link> </div> </li> ))} </ul>
    
          <p>
            <Link to="/createFruit"> <button>Nova Fruta</button>
            </Link>
          </p>
        </>
      )
    }
    
    export default FruitsList
    
    E simples assim,fizemos a query E populmos o component E com dados da API.Ainda fizemos um Returno simples ao usuário com feedback de loading e de erro,caso ocorra algum.
    Além disso,de antemão,apontamos rotas para cadaãoCRUDrelacionadaáfrutas.Vamos、agora、criar os components para cada aço para deposis conectar cada rotaáseu respectivo component.

    법은 일찍이 때가 많이 쌓였다


    Para seguir a ordem do acrônimo,vamos começar com o component de criaço:

    창조


    카미니오: frontend/src/components/CreateFruit.js
    import React from "react"
    import { gql, useMutation } from "@apollo/client"
    import { Link, useHistory } from "react-router-dom"
    import { GET_FRUITS } from "./Fruits"
    
    const CREATE_FRUIT = gql`
      mutation UpdateFruit(
        $name: String!
        $sugar: String!
        $calories: String!
      ) {
        createFruit(
          fruit: {
            name: $name
            nutritions: { sugar: $sugar, calories: $calories }
          }
        ) {
          id
          name
          nutritions {
            calories
            sugar
          }
        }
      }
    `
    
    const CreateFruit = () => {
      const history = useHistory()
    
      const [createFruit, { loading, error }] = useMutation(
        CREATE_FRUIT,
        {
          update(cache, { data: { createFruit } }) {
            const { fruits } = cache.readQuery({ query: GET_FRUITS })
            cache.writeQuery({
              query: GET_FRUITS,
              data: { fruits: fruits.concat([createFruit]) },
            })
          },
          onCompleted() {
            history.push(`/`)
          },
        }
      )
    
      if (loading) return <p>Loading...</p>
      if (error) return <p>Error :(</p>
    
      let nameInput
      let sugarInput
      let caloriesInput
    
      return (
        <div>
          <form
            className="App-viewbox"
            onSubmit={e => {
              e.preventDefault()
    
              createFruit({
                variables: {
                  name: nameInput.value,
                  sugar: sugarInput.value,
                  calories: caloriesInput.value,
                },
              })
    
              nameInput.value = ""
              sugarInput.value = ""
              caloriesInput.value = ""
            }}
          >
            <p>
              <label>
                Fruta
                <br />
                <input
                  type="text"
                  name="name"
                  ref={node => {
                    nameInput = node
                  }}
                />
              </label>
            </p>
            <p>
              <label>
                Açucar (g)
                <br />
                <input
                  type="text"
                  name="sugar"
                  ref={node => {
                    sugarInput = node
                  }}
                />
              </label>
            </p>
            <p>
              <label>
                Calorias
                <br />
                <input
                  type="text"
                  name="calories"
                  ref={node => {
                    caloriesInput = node
                  }}
                />
              </label>
            </p>
            <p className="App-close-btn">
              <Link to="/">
                <button></button>
              </Link>
            </p>
            <p>
              <button className="App-btn" type="submit">
                Salvar
              </button>
            </p>
          </form>
        </div>
      )
    }
    
    export default CreateFruit
    
    Neste componente criamosuma fruta usando 돌연변이, e atualizamoso cache do Apollo reutilizando a queryGET_FRUITS exposta emFruits.js.Para entender mais sobre esse assunto 영사관 adocumentação do Apollo client sobre mutations.
    Além disso,também tomamos vantagem do métodoonCompletedpara redirecionar a página para home deposis deposis de criar a fruta.

    읽다


    Agora,criaremos o componente de Visualizao.
    카미니오: frontend/src/components/Fruit.js
    import React from "react"
    import { gql, useQuery } from "@apollo/client"
    import { useParams, Link } from "react-router-dom"
    
    export const GET_FRUIT_BY_ID = gql`
      query GetFruit($id: ID!) {
        fruit(id: $id) {
          id
          name
          nutritions {
            sugar
            calories
          }
        }
      }
    `
    
    const Fruit = () => {
      const { id } = useParams()
      const { loading, error, data } = useQuery(GET_FRUIT_BY_ID, {
        variables: { id },
      })
    
      if (loading) return <p>Loading...</p>
      if (error) return <p>Error :(</p>
    
      return (
        <div className="App-viewbox">
          <p>
            <strong>Fruta: </strong>
            {data.fruit.name}
          </p>
          <p>
            <strong>Açucar: </strong>
            {data.fruit.nutritions.sugar}g
          </p>
          <p>
            <strong>Calorias: </strong>
            {data.fruit.nutritions.calories}kcal
          </p>
          <p className="App-close-btn">
            <Link to="/">
              <button></button>
            </Link>
          </p>
          <p>
            <Link to={`/editFruit/${id}`}>
              <button>Editar</button>
            </Link>
          </p>
        </div>
      )
    }
    
    export default Fruit
    
    Aqui a operaãoébem simples e passamos aidda fruta pela URL da rota usandouseParamsdo React router.

    현대화하다


    E, o 섹션:
    카미니오: frontend/src/components/EditFruit.js
    import React from "react"
    import { gql, useQuery, useMutation } from "@apollo/client"
    import { useParams, Link, useHistory } from "react-router-dom"
    import { GET_FRUIT_BY_ID } from "./Fruit"
    
    const UPDATE_FRUIT = gql`
      mutation UpdateFruit(
        $id: String!
        $name: String
        $sugar: String
        $calories: String
      ) {
        updateFruit(
          id: $id
          fruit: {
            name: $name
            nutritions: { sugar: $sugar, calories: $calories }
          }
        ) {
          id
          name
          nutritions {
            calories
            sugar
          }
        }
      }
    `
    
    const EditFruit = () => {
      const { id } = useParams()
      const history = useHistory()
    
      const { loading, error, data } = useQuery(GET_FRUIT_BY_ID, {
        variables: { id },
      })
      const [updateFruit, { error: mutationError }] = useMutation(
        UPDATE_FRUIT,
        {
          onCompleted() {
            history.push(`/`)
          },
        }
      )
    
      if (loading) return <p>Loading...</p>
      if (error || mutationError) return <p>Error :(</p>
    
      let nameInput
      let sugarInput
      let caloriesInput
    
      return (
        <div>
          <form
            className="App-viewbox"
            onSubmit={e => {
              e.preventDefault()
    
              updateFruit({
                variables: {
                  id: data.fruit.id,
                  name: nameInput.value,
                  sugar: sugarInput.value,
                  calories: caloriesInput.value,
                },
              })
            }}
          >
            <p>
              <label>
                Fruta
                <br />
                <input
                  type="text"
                  name="name"
                  defaultValue={data.fruit.name}
                  ref={node => {
                    nameInput = node
                  }}
                />
              </label>
            </p>
            <p>
              <label>
                Açucar (g)
                <br />
                <input
                  type="text"
                  name="sugar"
                  defaultValue={data.fruit.nutritions.sugar}
                  ref={node => {
                    sugarInput = node
                  }}
                />
              </label>
            </p>
            <p>
              <label>
                Calorias
                <br />
                <input
                  type="text"
                  name="calories"
                  defaultValue={data.fruit.nutritions.calories}
                  ref={node => {
                    caloriesInput = node
                  }}
                />
              </label>
            </p>
            <p className="App-close-btn">
              <Link to="/">
                <button type="button"></button>
              </Link>
            </p>
            <p>
              <button className="App-btn" type="submit">
                Salvar
              </button>
            </p>
          </form>
        </div>
      )
    }
    
    export default EditFruit
    
    Aqui também usamos par–metro vindo da rota para Identificationcaridda fruta e redirecionamos para home depois de finalizado.Assim como usamos a queryGET_FRUIT_BY_IDimportada do component de visualizaão.

    삭제하다


    E、 pra finalizar,criaremos o componente de deleção de fruta.
    카미니오: frontend/src/components/DeleteFruit.js
    import React from "react"
    import { gql, useQuery, useMutation } from "@apollo/client"
    import { useParams, Link, useHistory } from "react-router-dom"
    import { GET_FRUITS } from "./Fruits"
    import { GET_FRUIT_BY_ID } from "./Fruit"
    
    const DELETE_FRUIT = gql`
      mutation DeleteFruit($id: String) {
        deleteFruit(id: $id) {
          id
          name
          nutritions {
            calories
            sugar
          }
        }
      }
    `
    
    const DeleteFruit = () => {
      const history = useHistory()
      const { id } = useParams()
    
      const { loading, error, data } = useQuery(GET_FRUIT_BY_ID, {
        variables: { id },
      })
    
      const [deleteFruit, { error: mutationError }] = useMutation(
        DELETE_FRUIT,
        {
          update(cache) {
            const { fruits } = cache.readQuery({ query: GET_FRUITS })
    
            const deletedIndex = fruits.findIndex(
              fruit => fruit.id === id
            )
            const updatedCache = [
              ...fruits.slice(0, deletedIndex),
              ...fruits.slice(deletedIndex + 1, fruits.length),
            ]
            cache.writeQuery({
              query: GET_FRUITS,
              data: {
                fruits: updatedCache,
              },
            })
          },
          onCompleted() {
            history.push(`/`)
          },
        }
      )
    
      if (loading) return <p>Loading...</p>
      if (error || mutationError) return <p>Error :(</p>
    
      return (
        <div>
          <form
            className="App-viewbox"
            onSubmit={e => {
              e.preventDefault()
    
              deleteFruit({
                variables: { id },
              })
            }}
          >
            <p>
              Excluir <strong>{data.fruit.name}</strong>?
            </p>
            <p className="App-close-btn">
              <Link to="/">
                <button></button>
              </Link>
            </p>
            <p>
              <button className="App-btn" type="submit">
                Excluir
              </button>
            </p>
          </form>
        </div>
      )
    }
    
    export default DeleteFruit
    
    Aqui també mé manipulado cache do Apollo 클라이언트.deposis de removido o item,removemos o mesmo item do cache e relacionamos a queryGET_FRUITScom os dados atualizados.
    Crud feito com 성공!
    Não deixe de consultar a documentaço office do Apollo Cliente para maiores detalhes:
    🔗 www.apollographql.com/docs/react

    리칸토로타


    Agora para finalizar,ligamos cada rotaáseu componente.
    카미니오: frontend/src/routes.js
    import React from "react"
    import {
      BrowserRouter as Router,
      Switch,
      Route,
    } from "react-router-dom"
    
    import Fruits from "./components/Fruits"
    import Fruit from "./components/Fruit"import CreateFruit from "./components/CreateFruit"import EditFruit from "./components/EditFruit"import DeleteFruit from "./components/DeleteFruit"
    const Routes = () => (
      <Router>
        <Switch>
          <Route exact path="/">
            <Fruits />
          </Route>
          <Route path="/fruit/:id"> <Fruit /> </Route> <Route path="/createFruit"> <CreateFruit /> </Route> <Route path="/editFruit/:id"> <EditFruit /> </Route> <Route path="/deleteFruit/:id"> <DeleteFruit /> </Route> </Switch>
      </Router>
    )
    
    export default Routes
    

    결론


    E esse foi o tutorial,nesta jornada vocêaprendeu:
  • O queéGraphQL e como utilizálo
  • Como montaruma API em GraphQL usando 노드.js,Apollo Server e MongoDB
  • Como montar uma aplicaão frontend para consumir API GraphQL com React.js e Apollo 클라이언트.
  • Espero ter te ajudado!
    항목 링크:

  • Código no GitHub:github.com/emerson-pereira/frutas

  • Versão online da API:graphql-frutas.herokuapp.com

  • Vers ão 온라인 애플리케이션 React: codesandbox.io/s/graphql-frutas-4isf8
  • Originalmente publicado ememersonpereira.me

    좋은 웹페이지 즐겨찾기