Instalación
Para empezar hay que crear el servidor (npm init -y) y luego instalar los paquetes necesarios, GraphQL y Apollo de la siguiente manera.
npm i apollo-server graphql
Se utilizará
"type":"module"en elpackage.json
Coding
Para empezar crearemos el mockup de la base de datos en nuestro index.js para poder crear las llamadas de GraphQL, quedando de la siguiente manera.
const users = [
  {
    name: "Mattia",
    surname: "Parisi",
    phone: "119285475",
    city: "London",
    email: "mattiaparisi@gmail.com",
    id: "af7c1fe6-d669-414e-b066-e9733f0de7a8",
  },
  {
    name: "Alessia",
    surname: "Ciccarello",
    phone: "199951244",
    city: "Manchester",
    email: "alessiaciccarello@gmail.com",
    id: "08c71152-c552-42e7-b094-f510ff44e9cb",
  },
  {
    name: "Ciccio",
    surname: "Belo",
    phone: "0980981",
    city: "Buenos Aires",
    email: "cicciobelo@gmail.com",
    id: "c558a80a-f319-4c10-95d4-4282ef745b4b",
  },
  {
    name: "Santo",
    surname: "Terranova",
    phone: "81686585",
    city: "San Francisco",
    email: "santoterranova@gmail.com",
    id: "1ad1fccc-d279-46a0-8980-1d91afd6ba67",
  },
  {
    name: "Damiano",
    surname: "Pulvirenti",
    phone: "99991294919",
    city: "Dubai",
    email: "damianopulvirenti@gmail.com",
    id: "5108babc-bf35-44d5-a9ba-de08badfa80a",
  },
  {
    name: "Enrico",
    surname: "Bruno",
    city: "Barcelona",
    email: "enricobruno@gmail.com",
    id: "2d790a4d-7c9c-4e23-9c9c-5749c5fa7fdb",
  },
];
TypeDefs
Cada pedido que se hace a la base de datos se hace en base a un tipo definido de dato, para esto se utilizan los TypeDefs, gracias a estos podemos definir el tipo de datos que pediremos de la siguiente manera.
import { gql } from "apollo-server"
const users = [...];
const typeDefs = gql `
    type User {
        name: String!
        surname: String!
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers: [User]!
        oneUser: User!
        userCounter: Int!
    }
`
Como se puede ver, para definir los tipos de datos de la base de datos se utiliza un nombre cualquiera (User en este caso), cada uno de los datos que se devuelve tiene un tipado, sumado a esto tenemos que crear los llamados, para esto se utiliza el nombre reservado Query, dentro del cual indicamos los nombres de las llamadas y el tipo que de dato que devuelven.
Resolvers
Anteriormente creamos las llamadas para las peticiones, con esto hecho será necesario crear estas llamadas, a estas mismas se le llaman resolvers, y se crean usando la palabra reservada indicada anteriormente.
const resolvers = {
  Query: {
    allUsers: () => users,
    userCounter: () => users.length,
  },
};
Debemos indicar en cada uno que es lo que hace la petición, es por eso que se utiliza una función y el return para devolver los datos
Servidor
Por ultimo debemos iniciar nuestro servidor de Apollo, para esto debemos crear el mismo, pasándole los tipos y resolvers de la siguiente manera.
import { gql, ApolloServer } from 'apollo-server'
const users = [...];
const typeDefs = gql `
    type User {
        name: String!
        surname: String!
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers: [User]!
        oneUser: User!
        userCounter: Int!
    }
`
const resolvers = {
    Query: {
        allUsers: () => users,
        oneUser: (userID) => users.find(user => userID === user.id),
        userCounter: () => users.length
    }
}
const server = new ApolloServer({
    typeDefs,
    resolvers
})
server.listen().then(({ url }) => {
    console.log('listening on ' + url)
})
Esto nos dará lo necesario para hacer las primeras pruebas de queries en la url que nos generó.
Root
Gracias a la propiedad root que nos facilitan en los resolvers, podemos crear nuevos datos en base a los datos que ya existen, como por ejemplo, crear el dato de nombre completo sumando el nombre y el apellido, o para datos numéricos podemos sumar un precio y los impuestos del mismo para crear el precio final. Esto se hace agregando los datos con su tipo en los typeDefs, y el método en el resolver de la siguiente manera.
import { gql, ApolloServer } from 'apollo-server'
const users = [...];
const typeDefs = gql `
    type User {
        name: String!
        surname: String!
        fullName: String! # Agregamos el tipo de dato
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers: [User]!
        oneUser(name : String!): User!
        userCounter: Int!
    }
`
const resolvers = {
    Query: {
        allUsers: () => users,
        oneUser: (root, args) => {
            const { name } = args
            return users.find(user => user.name === name) || {}
        },
        userCounter: () => users.length
    },
    User: { // Agregamos el tipo User como un resolver
        fullName: (root) => `${root.name}, ${root.surname}` // Y creamos el dato usando el root como base
    }
}
const server = new ApolloServer({
    typeDefs,
    resolvers
})
server.listen().then(({ url }) => {
    console.log('listening on ' + url)
})
Mutations
Es posible agregar datos a nuestra base de datos, para esto será necesario usar las mutations, la cual es la forma que tiene GraphQL para manejar los datos.
import { gql, ApolloServer, UserInputError } from 'apollo-server' // Importamos los módulos para manejar errores
const users = [...];
const typeDefs = gql `
    type User {
        name: String!
        surname: String!
        fullName: String!
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers: [User]!
        oneUser(name : String!): User!
        userCounter: Int!
    }
    type Mutation { # Creamos la mutación
        addOneUser( # Con los datos que necesitamos
            name: String!
            surname: String!
            phone: String
            city: String!
            email: String!
        ) : User # Y el dato que devuelve
    }
`
const resolvers = {
    Query: {
        allUsers: () => users,
        oneUser: (root, args) => {
            const {
                name
            } = args
            return users.find(user => user.name === name) || {}
        },
        userCounter: () => users.length
    },
   
    Mutation: { // Creamos el resolver para las mutaciones
        addOneUser: (root, args) => {
            if (users.find(userInDb => userInDb.email === args.email)) {
                throw new UserInputError("User already exists in database") // Indicamos que devolvemos cuando ya existe el usuario
            }
            const newUser = {
                id: crypto.randomUUID(),
                ...args
            } // Sino lo creamos y lo subimos a la base de datos
            users.push(newUser)
            return newUser // Devolviendo el usuario
        }
    },
   
    User: {
        fullName: (root) => `${root.name}, ${root.surname}`
    }
}
const server = new ApolloServer({
    typeDefs,
    resolvers
})
server.listen().then(({ url }) => {
    console.log('listening on ' + url)
})
Sumado a esto también es posible usar el mutation para cambiar un dato, por ejemplo, si queremos cambiar el número de una persona podemos hacerlo de la siguiente manera.
import { gql, ApolloServer, UserInputError } from 'apollo-server'
const users = [...];
const typeDefs = gql `
    type User {
        name: String!
        surname: String!
        fullName: String!
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers(): [User]!
        oneUser(name : String!): User!
        userCounter: Int!
    }
    type Mutation {
        addOneUser(
            name: String!
            surname: String!
            phone: String
            city: String!
            email: String!
        ) : User
        changePhone(email: String!, phone: String!) : User
    }
`
const resolvers = {
    Query: {
        allUsers: () => users,
        oneUser: (root, args) => {
            const {
                name
            } = args
            return users.find(user => user.name === name) || {}
        },
        userCounter: () => users.length
    },
    Mutation: {
        addOneUser: (root, args) => {
            if (users.find(userInDb => userInDb.email === args.email)) {
                throw new UserInputError("User already exists in database")
            }
            const newUser = {
                id: crypto.randomUUID(),
                ...args
            }
            users.push(newUser)
            return newUser
        },
        changePhone: (root, {
            email,
            phone
        }) => {
            const userIndexInDB = users.findIndex(user => user.email === email)
            if (userIndexInDB === -1) return null
            const userFromDB = users[userIndexInDB]
            userFromDB.phone = phone
            users[userIndexInDB] = userFromDB
            return userFromDB
        }
    },
    User: {
        fullName: (root) => `${root.name}, ${root.surname}`
    }
}
const server = new ApolloServer({
    typeDefs,
    resolvers
})
server.listen().then(({ url }) => {
    console.log('listening on ' + url)
})
Enums
Es posible crear cierto rango de datos posibles para los parámetros que se le pasen a una query, para esto se hace uso de los enums, los cuales definen que valores puede recibir, eliminando la posibilidad de que puedan variar.
import { gql, ApolloServer, UserInputError } from 'apollo-server'
const users = [...];
const typeDefs = gql `
    enum YesNo { # Creamos el enum con sus opciones
        YES
        NO
    }
    type User {
        name: String!
        surname: String!
        fullName: String!
        phone: String
        city: String!
        email: String!
        id: ID!
    }
    type Query {
        allUsers(phone: YesNo): [User]! # Indicamos que podemos pasarle un dato
        oneUser(name : String!): User!
        userCounter: Int!
    }
    type Mutation {
        addOneUser(
            name: String!
            surname: String!
            phone: String
            city: String!
            email: String!
        ) : User
        changePhone(email: String!, phone: String!) : User
    }
`
const resolvers = {
    Query: {
        allUsers: (root, { phone }) => { // Creamos el resolver
            if (!phone) return users // Si no se pasa el enum, se envía todo
            return users.filter(user => phone === "YES" ? user.phone : !user.phone) // Sino se filtran los resultados
           
            /*
            En este caso tendría el mismo resultado
            if (phone === "YES") {
                return users.filter(user => user.phone)
            } else {
                return users.filter(user => !user.phone)
            }
            */
        },
        oneUser: (root, args) => {
            const {
                name
            } = args
            return users.find(user => user.name === name) || {}
        },
        userCounter: () => users.length
    },
    Mutation: {
        addOneUser: (root, args) => {
            if (users.find(userInDb => userInDb.email === args.email)) {
                throw new UserInputError("User already exists in database")
          }
            const newUser = {
                id: crypto.randomUUID(),
                ...args
            }
            users.push(newUser)
            return newUser
        },
        changePhone: (root, { email, phone }) => {
            const userIndexInDB = users.findIndex(user => user.email === email)
            if (userIndexInDB === -1) return null
            const userFromDB = users[userIndexInDB]
            userFromDB.phone = phone
            users[userIndexInDB] = userFromDB
            return userFromDB
        }
    },
    User: {
        fullName: (root) => `${root.name}, ${root.surname}`
    }
}
const server = new ApolloServer({
    typeDefs,
    resolvers
})
server.listen().then(({
    url
}) => {
    console.log('listening on ' + url)
})
Instalación del client
Con esto hecho podemos crear nuestro cliente para hacer los pedidos desde el front. Para empezar creamos un mockup de la base de datos que creamos con el paquete json-server de la siguiente manera.
npm i json-server
Lo siguiente sería crear un archivo bd.json a la altura de la raíz con nuestros datos.
{
  "users": [
    {
      "name": "Mattia",
      "surname": "Parisi",
      "phone": "119285475",
      "city": "London",
      "email": "mattiaparisi@gmail.com",
      "id": "af7c1fe6-d669-414e-b066-e9733f0de7a8"
    },
    {
      "name": "Alessia",
      "surname": "Ciccarello",
      "phone": "199951244",
      "city": "Manchester",
      "email": "alessiaciccarello@gmail.com",
      "id": "08c71152-c552-42e7-b094-f510ff44e9cb"
    },
    {
      "name": "Ciccio",
      "surname": "Belo",
      "phone": "0980981",
      "city": "Buenos Aires",
      "email": "cicciobelo@gmail.com",
      "id": "c558a80a-f319-4c10-95d4-4282ef745b4b"
    },
    {
      "name": "Santo",
      "surname": "Terranova",
      "phone": "81686585",
      "city": "San Francisco",
      "email": "santoterranova@gmail.com",
      "id": "1ad1fccc-d279-46a0-8980-1d91afd6ba67"
    },
    {
      "name": "Damiano",
      "surname": "Pulvirenti",
      "phone": "99991294919",
      "city": "Dubai",
      "email": "damianopulvirenti@gmail.com",
      "id": "5108babc-bf35-44d5-a9ba-de08badfa80a"
    },
    {
      "name": "Enrico",
      "surname": "Bruno",
      "city": "Barcelona",
      "email": "enricobruno@gmail.com",
      "id": "2d790a4d-7c9c-4e23-9c9c-5749c5fa7fdb"
    }
  ]
}
Con el archivo creado podemos organizar mejor las carpetas para crear el front, quedando las mismas de la siguiente manera.
server
 |
 |-> /node_modules
 |-> bd.json
 |-> index.js
 |-> package-lock.json
 |-> package.json
client
Ahora debemos abrir una consola en la raíz para crear el cliente con Vite.
npm create vite@latest
Indicamos que será React con TypeScript y que la carpeta será /client.
Por ultimo debemos instalar los paquetes necesarios para el cliente de Apollo en el front.
npm i @apollo/client graphql
Apollo-client
Luego de instalar el paquete deberemos ir a nuestro main.ts e importar el cliente de Apollo de la siguiente manera.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import {
  ApolloClient,
  HttpLink,
  InMemoryCache,
  ApolloProvider,
} from "@apollo/client"; // Importamos los módulos
const client = new ApolloClient({
  // Creamos el cliente
  cache: new InMemoryCache(), // Con la cache en memoria
  link: new HttpLink({
    uri: "http://localhost:4000", // Y le pasamos la url del servidor
  }),
});
ReactDOM.createRoot(document.getElementById("root")!).render(
  <ApolloProvider client={client}>
    {" "}
    {/* Envolvemos la app con el Provider */}    <React.StrictMode>
            <App />   {" "}
    </React.StrictMode> {" "}
  </ApolloProvider>,
);
La uri normalmente se debería pasar como una variable de entorno
Query de client
Apollo client nos da ciertos hooks para poder hacer queries desde el front, para ello será necesario usar useQuery en nuestro App.tsx de la siguiente manera.
import './App.css'
import { gql, useQuery } from '@apollo/client' // Importamos el hook
import type { UserFromDB } from './dict/Users'
const query = gql` # Creamos la query
  query {
    allUsers {
      fullName
      id
      phone
    }
  }
`
function App() {
  const { data, error, loading } = useQuery(query) // Tomamos las respuestas del hook
  return (
    <div>
      <h1>users</h1>
      {loading ? (
        <div>loading...</div> {/* Mostramos el componente si está cargando */}
      ) : (
        data?.allUsers.map((user: UserFromDB) => ( {/* O los datos */}
          <p key={user.id}>
            {user.fullName} - {user.phone}
          </p>
        ))
      )}
      {error && <div>{error.message}</div>}
    </div>
  )
}
export default App
Esta query se hace automáticamente cuando se inicia el proyecto, si queremos que la misma se haga solamente cuando nosotros los decidamos haremos uso del hook useLazyQuery de la siguiente manera.
import "./App.css";
import { gql, useLazyQuery } from "@apollo/client";
import type { UserFromDB } from "./dict/Users";
import { useEffect, useRef, useState } from "react";
const query = gql`
  query findUserByName($userNameToSearch: String!) { # Creamos la query con las variables
    oneUser(name: $userNameToSearch) {
      fullName
      id
      phone
      city
      email
    }
  }
`;
function App() {
  const [selectedUser, setSelectedUser] = useState<UserFromDB | null>(null);
  const usernameInput = useRef<HTMLInputElement | null>(null);
  const [oneUser, result] = useLazyQuery(query);
  const handleSearch = (e: React.MouseEvent<HTMLElement>, userName: string) => {
    // Creamos la función para buscar el usuario
    e.preventDefault();
    oneUser({ variables: { userNameToSearch: userName } }); // Y usamos la función de búsqueda con los parámetros como variables
  };
  useEffect(() => {
    if (result.data) setSelectedUser(result.data.oneUser); // Si existe el usuario se guarda en el estado
  }, [result.data]);
  if (selectedUser)
    return (
      <div>
               {" "}
        <h2>
                    {selectedUser.fullName} - 🌎 {selectedUser.city}       {" "}
        </h2>
                <span>{selectedUser.email}</span>
                <br />        <small>{selectedUser.phone}</small>       {" "}
        <button
          onClick={() => {
            setSelectedUser(null);
          }}
        >
                    close        {" "}
        </button>
             {" "}
      </div>
    );
  return (
    <div>
            <h1>users</h1>     {" "}
      <form>
                {result.error && <div>{result.error.message}</div>}{" "}
        {/* Si hay un error se muestra en pantalla */}
                <input type="text" ref={usernameInput} />        <button
          onClick={(e) =>
            handleSearch(e, usernameInput.current?.value as string)
          }
        >
          {" "}
          {/* Cuando se hace click se ejecuta la query */}          search      
           {" "}
        </button>     {" "}
      </form>
         {" "}
    </div>
  );
}
export default App;
Mutation del cliente
También es posible acceder a las mutaciones desde el cliente, de la misma forma que se hizo con las queries.
import "./App.css";
import { gql, useMutation, useQuery } from "@apollo/client";
import type { UserFromDB } from "./dict/Users";
import { useState } from "react";
const ADD_ONE_USER = gql` # Creamos la query para crear el usuario con sus variables
  mutation addOneUserToDB(
    $name: String!
    $surname: String!
    $phone: String
    $city: String!
    $email: String!
  ) {
    addOneUser(
      name: $name
      surname: $surname
      phone: $phone
      city: $city
      email: $email
    ) {
      id
      fullName
      city
      phone
      email
    }
  }
`;
const GET_ALL_USERS = gql` # Y la query para traer los datos
  query {
    allUsers {
      fullName
      email
      id
      city
      phone
    }
  }
`;
function App() {
  const [selectedUser, setSelectedUser] = useState<UserFromDB | null>(null);
  const [userDataToDB_name, setUserDataToDB_name] = useState<string>("");
  const [userDataToDB_surname, setUserDataToDB_surname] = useState<string>("");
  const [userDataToDB_phone, setUserDataToDB_phone] = useState<string>("");
  const [userDataToDB_city, setUserDataToDB_city] = useState<string>("");
  const [userDataToDB_email, setUserDataToDB_email] = useState<string>(""); // Creamos los estados para los datos
  const [errorFromGQL, setErrorFromGQL] = useState<string>("");
  const { data, loading, error } = useQuery(GET_ALL_USERS);
  const [addOneUserToDB] = useMutation(ADD_ONE_USER, {
    // Traemos la mutación y le pasamos la query
    refetchQueries: [{ query: GET_ALL_USERS }], // Le pasamos la query para traer los usuarios nuevamente cuando se hace la mutación
  });
  const handleSetUser = (user: UserFromDB): void => {
    setSelectedUser(user);
  };
  const handleAddUser = (e: React.MouseEvent<HTMLElement>): void => {
    e.preventDefault();
    addOneUserToDB({
      // Utilizamos la mutación
      variables: {
        name: userDataToDB_name,
        surname: userDataToDB_surname,
        phone: userDataToDB_phone,
        city: userDataToDB_city,
        email: userDataToDB_email,
      }, // Con los datos del usuario
    })
      .then(() => {
        setUserDataToDB_name("");
        setUserDataToDB_surname("");
        setUserDataToDB_city("");
        setUserDataToDB_phone("");
        setUserDataToDB_email("");
      }) // Y reiniciamos los formularios
      .catch((error) => {
        setErrorFromGQL(error.message); // Si hay un error lo guardamos por 5"
        setTimeout(() => {
          setErrorFromGQL("");
        }, 5000);
      });
  };
  if (selectedUser)
    return (
      <div>
               {" "}
        <h2>
                    {selectedUser.fullName} - 🌎 {selectedUser.city}       {" "}
        </h2>
                <span>{selectedUser.email}</span>
                <br />        <small>{selectedUser.phone}</small>       {" "}
        <button
          onClick={() => {
            setSelectedUser(null);
          }}
        >
                    close        {" "}
        </button>
             {" "}
      </div>
    );
  return (
    <div>
            <h1>users</h1>     {" "}
      <form>
                {errorFromGQL && <div>{errorFromGQL}</div>}{" "}
        {/* Si hay un error lo mostramos */}
                <input
          type="text"
          placeholder="name"
          value={userDataToDB_name}
          onChange={(e) => {
            setUserDataToDB_name(e.target.value);
          }}
        />        <input
          type="text"
          placeholder="surname"
          value={userDataToDB_surname}
          onChange={(e) => {
            setUserDataToDB_surname(e.target.value);
          }}
        />        <input
          type="text"
          placeholder="phone"
          value={userDataToDB_phone}
          onChange={(e) => {
            setUserDataToDB_phone(e.target.value);
          }}
        />        <input
          type="text"
          placeholder="city"
          value={userDataToDB_city}
          onChange={(e) => {
            setUserDataToDB_city(e.target.value);
          }}
        />        <input
          type="text"
          placeholder="email"
          value={userDataToDB_email}
          onChange={(e) => {
            setUserDataToDB_email(e.target.value);
          }}
        />        <button onClick={(e) => handleAddUser(e)}>add</button>   
         {" "}
      </form>
            {loading ? (
        <div>loading...</div>
      ) : (
        data?.allUsers.map((user: UserFromDB) => (
          <p key={user.id} onClick={() => handleSetUser(user)}>
                        {user.fullName} - {user.phone}         {" "}
          </p>
        ))
      )}      {error && <div>{error.message}</div>}   {" "}
    </div>
  );
}
export default App;
Organización
Teniendo todas las funcionalidades hechas podemos organizar todo en diferentes archivos, quedando los mismos de la siguiente manera.
src
 |-> assets
 |-> components
   |-> UserInput
     |-> UserInput.tsx
 |-> custom-hooks
  |-> useUserData.ts
 |-> dict
 |-> users
   |-> graphql-mutations.ts
   |-> graphql-queries.ts
Con esto organizado cada archivo quedaría de la siguiente manera.
components/UserInput/UserInput.tsx
type UserInputType = {
  inputPlaceholder: string;
  inputValue: string;
  onChangeFn: (param: string) => void;
};
const UserInput = ({
  inputPlaceholder,
  inputValue,
  onChangeFn,
}: UserInputType) => {
  return (
    <input
      type="text"
      placeholder={inputPlaceholder}
      value={inputValue}
      onChange={(e) => onChangeFn(e.target.value)}
    />
  );
};
export default UserInput;
custom-hooks/useUserData.ts
import { useState } from "react";
export const useUserData = () => {
  const [userDataToDB_name, setUserDataToDB_name] = useState<string>("");
  const [userDataToDB_surname, setUserDataToDB_surname] = useState<string>("");
  const [userDataToDB_phone, setUserDataToDB_phone] = useState<string>("");
  const [userDataToDB_city, setUserDataToDB_city] = useState<string>("");
  const [userDataToDB_email, setUserDataToDB_email] = useState<string>("");
  return {
    userData: {
      data: {
        name: userDataToDB_name,
        surname: userDataToDB_surname,
        phone: userDataToDB_phone,
        city: userDataToDB_city,
        email: userDataToDB_email,
      },
      functions: {
        setName: setUserDataToDB_name,
        setSurname: setUserDataToDB_surname,
        setPhone: setUserDataToDB_phone,
        setCity: setUserDataToDB_city,
        setEmail: setUserDataToDB_email,
      },
    },
  };
};
users/graphql-mutations.ts
import { gql } from "@apollo/client";
export const ADD_ONE_USER = gql`
  mutation addOneUserToDB(
    $name: String!
    $surname: String!
    $phone: String
    $city: String!
    $email: String!
  ) {
    addOneUser(
      name: $name
      surname: $surname
      phone: $phone
      city: $city
      email: $email
    ) {
      id
      fullName
      city
      phone
      email
    }
  }
`;
users/graphql-queries.ts
import { gql } from "@apollo/client";
export const GET_ALL_USERS = gql`
  query {
    allUsers {
      fullName
      email
      id
      city
      phone
    }
  }
`;
export const GET_ONE_USER = gql`
  query findUserByName($userNameToSearch: String!) {
    oneUser(name: $userNameToSearch) {
      fullName
      id
      phone
      city
      email
    }
  }
`;
App.tsx
import "./App.css";
import { useMutation, useQuery } from "@apollo/client";
import type { UserFromDB } from "./dict/Users";
import { useState } from "react";
import { GET_ALL_USERS } from "./users/graphql-queries";
import { ADD_ONE_USER } from "./users/graphql-mutations";
import { useUserData } from "./custom-hooks/useUserData";
import UserInput from "./components/UserInput/UserInput";
function App() {
  const [selectedUser, setSelectedUser] = useState<UserFromDB | null>(null);
  const [errorFromGQL, setErrorFromGQL] = useState("");
  const { userData } = useUserData();
  const { data, loading, error } = useQuery(GET_ALL_USERS);
  const [addOneUserToDB] = useMutation(ADD_ONE_USER, {
    refetchQueries: [{ query: GET_ALL_USERS }],
  });
  const handleSetUser = (user: UserFromDB): void => {
    setSelectedUser(user);
  };
  const handleAddUser = (e: React.MouseEvent<HTMLElement>) => {
    e.preventDefault();
    addOneUserToDB({
      variables: {
        name: userData.data.name,
        surname: userData.data.surname,
        phone: userData.data.phone,
        city: userData.data.city,
        email: userData.data.email,
      },
    })
      .then(() => {
        userData.functions.setName("");
        userData.functions.setSurname("");
        userData.functions.setCity("");
        userData.functions.setPhone("");
        userData.functions.setEmail("");
      })
      .catch((error) => {
        setErrorFromGQL(error.message);
        setTimeout(() => {
          setErrorFromGQL("");
        }, 5000);
      });
  };
  if (selectedUser)
    return (
      <div>
               {" "}
        <h2>
                    {selectedUser.fullName} - 🌎 {selectedUser.city}       {" "}
        </h2>
                <span>{selectedUser.email}</span>
                <br />        <small>{selectedUser.phone}</small>       {" "}
        <button
          onClick={() => {
            setSelectedUser(null);
          }}
        >
                    close        {" "}
        </button>
             {" "}
      </div>
    );
  return (
    <div>
            <h1>users</h1>     {" "}
      <form>
                {errorFromGQL && <div>{errorFromGQL}</div>}
                <UserInput
          inputPlaceholder="name"
          inputValue={userData.data.name}
          onChangeFn={userData.functions.setName}
        />        <UserInput
          inputPlaceholder="surname"
          inputValue={userData.data.surname}
          onChangeFn={userData.functions.setSurname}
        />        <UserInput
          inputPlaceholder="phone"
          inputValue={userData.data.phone}
          onChangeFn={userData.functions.setPhone}
        />        <UserInput
          inputPlaceholder="city"
          inputValue={userData.data.city}
          onChangeFn={userData.functions.setCity}
        />        <UserInput
          inputPlaceholder="email"
          inputValue={userData.data.email}
          onChangeFn={userData.functions.setEmail}
        />        <button onClick={(e) => handleAddUser(e)}>add</button>   
         {" "}
      </form>
            {loading ? (
        <div>loading...</div>
      ) : (
        data?.allUsers.map((user: UserFromDB) => (
          <p key={user.id} onClick={() => handleSetUser(user)}>
                        {user.fullName} - {user.phone}         {" "}
          </p>
        ))
      )}      {error && <div>{error.message}</div>}   {" "}
    </div>
  );
}
export default App;