Não devo ter medo. O medo é o assassino da mente. Medo é a pequena morte que traz obliteração total. Eu irei enfrentar o meu medo. Permitirei que passe sobre mim e através de mim. E quando tiver passado, voltarei o olho interior para ver seu caminho. Para onde foi o medo, não haverá nada. Só eu vou ficar.
– “Ladainha contra o medo”, Frank Herbert, Dune

Você pode estar se perguntando: “O que o medo tem a ver com um aplicativo React?” Primeiro de tudo, não há nada a temer em um aplicativo React. De fato, nesse aplicativo em particular, banimos o medo. Isso não é legal?

Agora que você está pronto para ser destemido, vamos discutir nosso aplicativo. É um mini clone do Yelp, onde, em vez de revisar restaurantes, os usuários revisam planetas da clássica série de ficção científica, Duna. (Por quê? Porque há um novo filme do Dune saindo … mas voltando ao ponto principal.)

Para criar nosso aplicativo de pilha completa, usaremos tecnologias que facilitam nossas vidas.

  1. Reagir: Estrutura de front-end intuitiva e composicional, porque nosso cérebro gosta de compor coisas.
  2. GraphQL: Você pode ter ouvido muitas razões pelas quais o GraphQL é incrível. De longe, o mais importante é produtividade e felicidade do desenvolvedor.
  3. Hasura: Configure uma API GraphQL gerada automaticamente sobre um banco de dados Postgres em menos de 30 segundos.
  4. Heroku: Para hospedar nosso banco de dados.

E GraphQL me dá felicidade como?

Vejo que você é cético. Mas você provavelmente voltará assim que passar algum tempo com o GraphiQL (o playground do GraphQL).

O uso do GraphQL é muito fácil para o desenvolvedor front-end, comparado com as formas antigas de pontos de extremidade REST desajeitados. O GraphQL fornece um único ponto de extremidade que escuta todos os seus problemas … quero dizer, consultas. É um ouvinte tão bom que você pode dizer exatamente o que deseja, e isso dará a você, nada menos e nada mais.

Sentindo-se empolgado com essa experiência terapêutica? Vamos mergulhar no tutorial para que você possa experimentá-lo o mais rápido possível!

👉🏽 Aqui está o repo se você quiser codificar.

Step 1: Dimplantar no Heroku

O primeiro passo de toda boa jornada é sentar com um chá quente e beber com calma. Uma vez feito isso, podemos implantar no Heroku a partir do Site da Hasura. Isso nos preparará tudo o que precisamos: um banco de dados Postgres, nosso mecanismo Hasura GraphQL e alguns lanches para a jornada.

black-books.png
Nem uma referência a Dune

Etapa 2: criar tabela de planetas

Nossos usuários querem revisar planetas. Por isso, criamos uma tabela do Postgres através do console Hasura para armazenar nossos dados do planeta. Digno de nota é o planeta maligno, Giedi Prime, que vem chamando a atenção com sua culinária não convencional.

Tabela de planetas

Enquanto isso, na guia GraphiQL: Hasura gerou automaticamente nosso esquema GraphQL! Brinque com o Explorer aqui 👇🏽

GraphiQL Explorer

Step 3: Creagir Reagir aplicativo

Precisamos de uma interface do usuário para nosso aplicativo, por isso criamos um aplicativo React e instalamos algumas bibliotecas para solicitações, roteamento e estilos do GraphQL. (Assegure-se de ter instalado primeiro.)

> npx create-react-app melange> cd melange> npm install graphql @apollo/client react-router-dom @emotion/styled @emotion/core> npm start
terminal

Step 4: SApollo Client

Cliente Apollo nos ajudará com nossas solicitações de rede e cache do GraphQL, para que possamos evitar todo esse trabalho pesado. Também fazemos nossa primeira consulta e listamos nossos planetas! Nosso aplicativo está começando a se moldar.

import React from "react";import { render } from "react-dom";import { ApolloProvider } from "@apollo/client";import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";import Planets from "./components/Planets";const client = new ApolloClient({  cache: new InMemoryCache(),  link: new HttpLink({    uri: "[YOUR HASURA GRAPHQL ENDPOINT]",  }),});const App = () => (        );render(, document.getElementById("root"));
index.js

Testamos nossa consulta GraphQL no console Hasura antes de copiá-la em nosso código.

p1 s4 test query 2

import React from "react";import { useQuery, gql } from "@apollo/client";const PLANETS = gql`  {    planets {      id      name      cuisine    }  }`;const Planets = ({ newPlanets }) => {  const { loading, error, data } = useQuery(PLANETS);  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; return data.planets.map(({id, name, cuisine}) => (

{name} | {cuisine}

));};export default Planets;
Planets.js

Step 5: Slista de estilos

Nossa lista de planetas é boa e tudo, mas precisa de uma pequena reforma com Emoção (Vejo repo para estilos completos).

Lista estilizada de planetas

Step 6: Search formulário e estado

Nossos usuários querem procurar planetas e ordená-los por nome. Portanto, adicionamos um formulário de pesquisa que consulta nosso endpoint com uma string de pesquisa e passamos os resultados para Planets para atualizar nossa lista de planetas. Nós também usamos Ganchos de reação para gerenciar nosso estado do aplicativo.

import React, { useState } from "react";import { useLazyQuery, gql } from "@apollo/client";import Search from "./Search";import Planets from "./Planets";const SEARCH = gql`  query Search($match: String) {    planets(order_by: { name: asc }, where: { name: { _ilike: $match } }) {      name      cuisine      id    }  }`;const PlanetSearch = () => {  const [inputVal, setInputVal] = useState("");  const [search, { loading, error, data }] = useLazyQuery(SEARCH);  return (    
setInputVal(e.target.value)} onSearch={() => search({ variables: { match: `%${inputVal}%` } })} />
);};export default PlanetSearch;
PlanetSearch.js

import React from "react";import { useQuery, gql } from "@apollo/client";import { List, ListItem } from "./shared/List";import { Badge } from "./shared/Badge";const PLANETS = gql`  {    planets {      id      name      cuisine    }  }`;const Planets = ({ newPlanets }) => {  const { loading, error, data } = useQuery(PLANETS);  const renderPlanets = (planets) => {    return planets.map(({ id, name, cuisine }) => (              {name} {cuisine}          ));  };  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; return {renderPlanets(newPlanets || data.planets)};};export default Planets;
Planets.js

import React from "react";import styled from "@emotion/styled";import { Input, Button } from "./shared/Form";const SearchForm = styled.div`  display: flex;  align-items: center;  > button {    margin-left: 1rem;  }`;const Search = ({ inputVal, onChange, onSearch }) => {  return (                      );};export default Search;
Search.js

import React from "react";import { render } from "react-dom";import { ApolloProvider } from "@apollo/client";import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";import PlanetSearch from "./components/PlanetSearch";import Logo from "./components/shared/Logo";import "./index.css";const client = new ApolloClient({  cache: new InMemoryCache(),  link: new HttpLink({    uri: "[YOUR HASURA GRAPHQL ENDPOINT]",  }),});const App = () => (            );render(, document.getElementById("root"));
index.js

Step 7: Be orgulhoso

Já implementamos nossa lista de planetas e recursos de pesquisa! Com amor, contemplamos nossa obra, tiramos algumas selfies e passamos a fazer críticas.

Lista de planetas com pesquisa

Step 1: Ctabela de comentários

Nossos usuários visitarão esses planetas e escreverão críticas sobre sua experiência. Criamos uma tabela via console Hasura para nossos dados de revisão.

Tabela de comentários

Adicionamos uma chave estrangeira do planet_id coluna para o id coluna na planets tabela, para indicar que planet_ids de reviews tem que combinar idde planets.

Chaves estrangeiras

Step 2: Trelações de rack

Cada planeta tem várias revisões, enquanto cada revisão tem um planeta: um relacionamento um para muitos. Criamos e rastreamos esse relacionamento por meio do console Hasura, para que ele possa ser exposto em nosso esquema GraphQL.

Rastreando relacionamentos

Agora podemos consultar revisões para cada planeta no Explorer!

Consultando planeta comentários

Step 3: Sencaminhamento

Queremos poder clicar em um planeta e visualizar suas resenhas em uma página separada. Configuramos o roteamento com o React Router e listamos as análises na página do planeta.

import React from "react";import { render } from "react-dom";import { ApolloProvider } from "@apollo/client";import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";import { BrowserRouter, Switch, Route } from "react-router-dom";import PlanetSearch from "./components/PlanetSearch";import Planet from "./components/Planet";import Logo from "./components/shared/Logo";import "./index.css";const client = new ApolloClient({  cache: new InMemoryCache(),  link: new HttpLink({    uri: "[YOUR HASURA GRAPHQL ENDPOINT]",  }),});const App = () => (                                              );render(, document.getElementById("root"));
index.js

import React from "react";import { useQuery, gql } from "@apollo/client";import { List, ListItem } from "./shared/List";import { Badge } from "./shared/Badge";const PLANET = gql`  query Planet($id: uuid!) {    planets_by_pk(id: $id) {      id      name      cuisine      reviews {        id        body      }    }  }`;const Planet = ({  match: {    params: { id },  },}) => {  const { loading, error, data } = useQuery(PLANET, {    variables: { id },  });  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; const { name, cuisine, reviews } = data.planets_by_pk; return (

{name} {cuisine}

{reviews.map((review) => ( {review.body} ))}
);};export default Planet;
Planet.js

import React from "react";import { useQuery, gql } from "@apollo/client";import { Link } from "react-router-dom";import { List, ListItemWithLink } from "./shared/List";import { Badge } from "./shared/Badge";const PLANETS = gql`  {    planets {      id      name      cuisine    }  }`;const Planets = ({ newPlanets }) => {  const { loading, error, data } = useQuery(PLANETS);  const renderPlanets = (planets) => {    return planets.map(({ id, name, cuisine }) => (                        {name} {cuisine}                  ));  };  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; return {renderPlanets(newPlanets || data.planets)};};export default Planets;
Planets.js

Step 4: Se mais assinaturas

Instalamos novas bibliotecas e configuramos o Apollo Client para suportar assinaturas. Em seguida, alteramos nossa consulta de comentários para uma assinatura para que ela possa mostrar atualizações ao vivo.

> npm install @apollo/link-ws subscriptions-transport-ws

import React from "react";import { render } from "react-dom";import {  ApolloProvider,  ApolloClient,  HttpLink,  InMemoryCache,  split,} from "@apollo/client";import { getMainDefinition } from "@apollo/client/utilities";import { WebSocketLink } from "@apollo/link-ws";import { BrowserRouter, Switch, Route } from "react-router-dom";import PlanetSearch from "./components/PlanetSearch";import Planet from "./components/Planet";import Logo from "./components/shared/Logo";import "./index.css";const GRAPHQL_ENDPOINT = "[YOUR HASURA GRAPHQL ENDPOINT]";const httpLink = new HttpLink({  uri: `https://${GRAPHQL_ENDPOINT}`,});const wsLink = new WebSocketLink({  uri: `ws://${GRAPHQL_ENDPOINT}`,  options: {    reconnect: true,  },});const splitLink = split(  ({ query }) => {    const definition = getMainDefinition(query);    return (      definition.kind === "OperationDefinition" &&      definition.operation === "subscription"    );  },  wsLink,  httpLink);const client = new ApolloClient({  cache: new InMemoryCache(),  link: splitLink,});const App = () => (                                              );render(, document.getElementById("root"));
index.js

import React from "react";import { useSubscription, gql } from "@apollo/client";import { List, ListItem } from "./shared/List";import { Badge } from "./shared/Badge";const PLANET = gql`  subscription Planet($id: uuid!) {    planets_by_pk(id: $id) {      id      name      cuisine      reviews {        id        body      }    }  }`;const Planet = ({  match: {    params: { id },  },}) => {  const { loading, error, data } = useSubscription(PLANET, {    variables: { id },  });  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; const { name, cuisine, reviews } = data.planets_by_pk; return (

{name} {cuisine}

{reviews.map((review) => ( {review.body} ))}
);};export default Planet;
Planet.js

Página do planeta com críticas ao vivo

Step 5: Do uma dança de verme da areia

Implementamos planetas com análises ao vivo! Faça uma pequena dança para comemorar antes de começar a falar sério.

Dança verme

Step 1: Aformulário de entrada dd

Queremos uma maneira de enviar comentários por meio da interface do usuário. Renomeamos nosso formulário de pesquisa como genérico InputForm e adicione-o acima da lista de revisão.

import React, { useState } from "react";import { useSubscription, gql } from "@apollo/client";import { List, ListItem } from "./shared/List";import { Badge } from "./shared/Badge";import InputForm from "./shared/InputForm";const PLANET = gql`  subscription Planet($id: uuid!) {    planets_by_pk(id: $id) {      id      name      cuisine      reviews(order_by: { created_at: desc }) {        id        body        created_at      }    }  }`;const Planet = ({  match: {    params: { id },  },}) => {  const [inputVal, setInputVal] = useState("");  const { loading, error, data } = useSubscription(PLANET, {    variables: { id },  });  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; const { name, cuisine, reviews } = data.planets_by_pk; return (

{name} {cuisine}

setInputVal(e.target.value)} onSubmit={() => {}} buttonText="Submit" /> {reviews.map((review) => ( {review.body} ))}
);};export default Planet;
Planet.js

Step 2: Tmutação mais crítica

Usaremos uma mutação para adicionar novos comentários. Testamos nossa mutação com o GraphiQL no console Hasura.

Inserir mutação de revisão no GraphiQL

E converta-o para aceitar variáveis ​​para que possamos usá-lo em nosso código.

Inserir mutação de revisão com variáveis

Step 3: Cação real

o Bene Gesserit nos pediram para não permitir (tosse censurar tosse) a palavra “medo” nas revisões. Criamos uma ação para a lógica comercial que procurará essa palavra sempre que um usuário enviar uma revisão.

p3 s3 derive action 2

Dentro de nossa ação recém-criada, vamos para a guia “Codegen”.

p3 s3 codegen 2

Selecionamos a opção nodejs-express e copiamos o código do manipulador abaixo.

Código padrão para nodejs-express

Clicamos em “Experimente o Glitch”, que nos leva a um aplicativo expresso barebones, onde podemos colar nosso código de manipulador.

Colar nosso código de manipulador no Glitch

De volta à ação, definimos o URL do manipulador como aquele do nosso aplicativo Glitch, com a rota correta do código do manipulador.

URL do manipulador

Agora podemos testar nossa ação no console. Ele funciona como uma mutação regular, porque ainda não temos nenhuma lógica de negócios que verifique a palavra “medo”.

Testando nossa ação no console

Step 4: Alógica de negócios dd

Em nosso manipulador, adicionamos lógica de negócios que verifica “medo” dentro do corpo da revisão. Se for destemido, executamos a mutação como de costume. Caso contrário, retornamos um erro sinistro.

Lógica de negócios que verifica

Se executarmos a ação com “medo” agora, obteremos o erro na resposta:

Testando nossa lógica de negócios no console

Step 5: Order comentários

Atualmente, nosso pedido de revisão é de alta qualidade. Nós adicionamos um created_at coluna para o reviews tabela para que possamos pedir o mais novo primeiro.

reviews(order_by: { created_at: desc })

Step 6: Amutação dd review

Por fim, atualizamos nossa sintaxe de ação com variáveis ​​e copiamos e colamos em nosso código como uma mutação. Atualizamos nosso código para executar essa mutação quando um usuário envia uma nova revisão, para que nossa lógica de negócios possa verificar a conformidade (ahem obediência ahem) antes de atualizar nosso banco de dados.

import React, { useState } from "react";import { useSubscription, useMutation, gql } from "@apollo/client";import { List, ListItem } from "./shared/List";import { Badge } from "./shared/Badge";import InputForm from "./shared/InputForm";const PLANET = gql`  subscription Planet($id: uuid!) {    planets_by_pk(id: $id) {      id      name      cuisine      reviews(order_by: { created_at: desc }) {        id        body        created_at      }    }  }`;const ADD_REVIEW = gql`  mutation($body: String!, $id: uuid!) {    AddFearlessReview(body: $body, id: $id) {      affected_rows    }  }`;const Planet = ({  match: {    params: { id },  },}) => {  const [inputVal, setInputVal] = useState("");  const { loading, error, data } = useSubscription(PLANET, {    variables: { id },  });  const [addReview] = useMutation(ADD_REVIEW);  if (loading) return 

Loading ...

; if (error) return

Error 🙁

; const { name, cuisine, reviews } = data.planets_by_pk; return (

{name} {cuisine}

setInputVal(e.target.value)} onSubmit={() => { addReview({ variables: { id, body: inputVal } }) .then(() => setInputVal("")) .catch((e) => { setInputVal(e.message); }); }} buttonText="Submit" /> {reviews.map((review) => ( {review.body} ))}
);};export default Planet;
Planet.js

Se enviarmos uma nova revisão que inclua “medo” agora, obteremos nosso erro sinistro, que exibimos no campo de entrada.

Testando nossa ação através da interface do usuário

Etapa 7: conseguimos! 🎉

Parabéns por criar um aplicativo React e GraphQL de pilha cheia!

Toca aqui

spice_must_flow.jpg

Se ao menos tivéssemos alguma mistura de especiarias, saberíamos. Mas criamos tantos recursos em tão pouco tempo! Cobrimos consultas, mutações, assinaturas, roteamento, pesquisa e até lógica comercial personalizada do GraphQL com as ações do Hasura! Espero que você tenha se divertido codificando junto.

Quais outros recursos você gostaria de ver neste aplicativo? Entre em contato comigo no Twitter e eu farei mais tutoriais! Se você está inspirado para adicionar recursos, por favor compartilhe – Eu adoraria ouvir sobre eles 🙂