tutoriales.com

React Native y GraphQL: Construyendo Aplicaciones Escalables con Apollo Client

Este tutorial te guiará a través de la integración de GraphQL en tus aplicaciones React Native utilizando Apollo Client. Aprenderás a configurar Apollo, realizar consultas, mutaciones y suscripciones, y gestionar el estado de manera eficiente para construir aplicaciones escalables y de alto rendimiento.

Intermedio25 min de lectura16 views
Reportar error

¡Hola, desarrollador! 👋 ¿Estás listo para llevar tus aplicaciones React Native al siguiente nivel en cuanto a gestión de datos y comunicación con el backend? Si la respuesta es sí, ¡has llegado al lugar correcto! En este tutorial, exploraremos a fondo cómo integrar GraphQL en tus proyectos React Native, utilizando la potente y popular librería Apollo Client.

GraphQL se ha convertido en una alternativa robusta a las API REST tradicionales, ofreciendo una forma más eficiente, potente y flexible de gestionar los datos de tus aplicaciones. Con GraphQL, tus clientes pueden especificar exactamente los datos que necesitan, eliminando la sobrecarga o la subcarga de datos.


🚀 ¿Por qué GraphQL y React Native?

React Native es ideal para construir aplicaciones móviles multiplataforma, y GraphQL es el complemento perfecto para una gestión de datos eficiente. Aquí te presento algunas razones clave:

  • Flexibilidad de Datos: Pides exactamente lo que necesitas, ni más ni menos. Esto reduce el tamaño de la respuesta y el tráfico de red, crucial en entornos móviles.
  • Desarrollo Rápido: GraphQL permite a los equipos frontend y backend trabajar de forma más independiente. El frontend define sus necesidades y el backend se encarga de satisfacerlas.
  • Agregación de Datos: Una sola solicitud GraphQL puede obtener datos de múltiples recursos, eliminando la necesidad de múltiples llamadas REST.
  • Evolución sin Versiones: Añadir nuevos campos o tipos a tu API GraphQL no requiere versiones nuevas, lo que simplifica el mantenimiento.
  • Potente Tipado: El sistema de tipos de GraphQL mejora la validación, la introspección y la autocompletado en tu IDE.
💡 Consejo: GraphQL no es un reemplazo de REST para todos los casos. Evalúa tus necesidades. Para aplicaciones con requisitos de datos complejos o en evolución constante, GraphQL brilla.

🛠️ Herramientas Necesarias

Antes de sumergirnos en el código, asegúrate de tener lo siguiente:

  • Node.js y npm/Yarn: Entorno de ejecución y gestor de paquetes.
  • React Native CLI o Expo CLI: Para crear y gestionar tu proyecto React Native.
  • Un editor de código: Visual Studio Code con extensiones GraphQL es altamente recomendado.
  • Conocimientos básicos de React Native: Componentes, useState, useEffect.
  • Acceso a una API GraphQL: Para este tutorial, usaremos una API pública o simularemos una con Apollo Server (veremos la configuración básica).

🎯 Configuración Inicial del Proyecto React Native

Si aún no tienes un proyecto React Native, crea uno. Usaremos Expo CLI por su facilidad de configuración.

  1. Crea un nuevo proyecto Expo:
npx create-expo-app MyGraphQLApp
cd MyGraphQLApp
  1. Instala las dependencias de Apollo Client:
npm install @apollo/client graphql
# o si usas yarn
yarn add @apollo/client graphql
*   `@apollo/client`: El cliente principal de Apollo para interactuar con GraphQL.
*   `graphql`: La librería core de GraphQL, necesaria para parsear tus consultas.

🔗 Conectando Apollo Client a tu Aplicación

El primer paso es inicializar Apollo Client y envolver tu aplicación con ApolloProvider. Esto hará que el cliente esté disponible para todos tus componentes.

Crea un nuevo archivo apollo.js (o apollo.ts si usas TypeScript) en la raíz de tu proyecto o en una carpeta src/config.

// apollo.js
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.graphql.guide/graphql', // Reemplaza con la URL de tu API GraphQL
  cache: new InMemoryCache(),
});

export default client;

Ahora, envuelve tu componente App con ApolloProvider en App.js:

// App.js
import React from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { ApolloProvider } from '@apollo/client';
import client from './apollo'; // Importa el cliente Apollo configurado

// Componente de ejemplo que usará GraphQL
import PostsList from './components/PostsList'; 

export default function App() {
  return (
    <ApolloProvider client={client}>
      <View style={styles.container}>
        <Text style={styles.header}>🚀 Mi Aplicación GraphQL con React Native</Text>
        <PostsList />
        <StatusBar style="auto" />
      </View>
    </ApolloProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    paddingTop: 50,
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
  },
});

En este punto, tu aplicación ya está lista para comunicarse con una API GraphQL.


📝 Realizando Consultas (Queries)

Las consultas son la forma en que solicitas datos de tu servidor GraphQL. Apollo Client proporciona el hook useQuery para simplificar este proceso.

Ejemplo: Obtener una Lista de Posts

Vamos a crear un componente PostsList.js para mostrar una lista de posts.

// components/PostsList.js
import React from 'react';
import { View, Text, FlatList, ActivityIndicator, StyleSheet } from 'react-native';
import { gql, useQuery } from '@apollo/client';

// 1. Define tu consulta GraphQL
const GET_POSTS = gql`
  query GetPosts {
    allPosts {
      id
      title
      body
      user {
        id
        username
      }
    }
  }
`;

const PostsList = () => {
  // 2. Usa el hook useQuery
  const { loading, error, data } = useQuery(GET_POSTS);

  if (loading) return <ActivityIndicator size="large" color="#0000ff" />;
  if (error) return <Text style={styles.errorText}>Error al cargar posts: {error.message}</Text>;

  return (
    <View style={styles.container}>
      <Text style={styles.listHeader}>Publicaciones Recientes</Text>
      <FlatList
        data={data.allPosts}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View style={styles.postItem}>
            <Text style={styles.postTitle}>{item.title}</Text>
            <Text style={styles.postBody}>{item.body.substring(0, 100)}...</Text>
            {item.user && <Text style={styles.postAuthor}>Autor: {item.user.username}</Text>}
          </View>
        )}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    width: '100%',
    paddingHorizontal: 20,
  },
  listHeader: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 15,
    textAlign: 'center',
  },
  postItem: {
    backgroundColor: '#f0f0f0',
    padding: 15,
    borderRadius: 8,
    marginBottom: 10,
    borderColor: '#ddd',
    borderWidth: 1,
  },
  postTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  postBody: {
    fontSize: 14,
    color: '#555',
    marginBottom: 5,
  },
  postAuthor: {
    fontSize: 12,
    fontStyle: 'italic',
    color: '#888',
  },
  errorText: {
    color: 'red',
    fontSize: 16,
  }
});

export default PostsList;
📌 Nota: La API `https://api.graphql.guide/graphql` es una API de ejemplo y puede no tener todos los campos exactos. Adapta la consulta `GET_POSTS` a la estructura real de tu API.

🧩 Consultas con Variables

A menudo, necesitarás pasar argumentos a tus consultas, como un id para obtener un elemento específico. Esto se hace usando variables.

// components/PostDetail.js
import React from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { gql, useQuery } from '@apollo/client';

const GET_POST_DETAIL = gql`
  query GetPostDetail($postId: ID!) {
    post(id: $postId) {
      id
      title
      body
      comments {
        id
        name
        body
      }
      user {
        username
      }
    }
  }
`;

const PostDetail = ({ postId }) => {
  const { loading, error, data } = useQuery(GET_POST_DETAIL, {
    variables: { postId }, // Pasa las variables aquí
  });

  if (loading) return <ActivityIndicator size="large" color="#0000ff" />;
  if (error) return <Text style={styles.errorText}>Error al cargar el post: {error.message}</Text>;
  if (!data || !data.post) return <Text>Post no encontrado.</Text>;

  const { post } = data;

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{post.title}</Text>
      {post.user && <Text style={styles.author}>Por: {post.user.username}</Text>}
      <Text style={styles.body}>{post.body}</Text>
      
      {post.comments && post.comments.length > 0 && (
        <View style={styles.commentsSection}>
          <Text style={styles.commentsHeader}>Comentarios:</Text>
          {post.comments.map(comment => (
            <View key={comment.id} style={styles.commentItem}>
              <Text style={styles.commentName}>{comment.name}</Text>
              <Text style={styles.commentBody}>{comment.body}</Text>
            </View>
          ))}
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  author: {
    fontSize: 14,
    fontStyle: 'italic',
    color: '#666',
    marginBottom: 15,
  },
  body: {
    fontSize: 16,
    lineHeight: 24,
    marginBottom: 20,
  },
  commentsSection: {
    marginTop: 20,
    borderTopWidth: 1,
    borderTopColor: '#eee',
    paddingTop: 15,
  },
  commentsHeader: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  commentItem: {
    backgroundColor: '#f9f9f9',
    padding: 10,
    borderRadius: 5,
    marginBottom: 10,
    borderWidth: 1,
    borderColor: '#eee',
  },
  commentName: {
    fontWeight: 'bold',
    marginBottom: 3,
  },
  commentBody: {
    fontSize: 14,
  },
  errorText: {
    color: 'red',
    fontSize: 16,
  }
});

export default PostDetail;

Para usar PostDetail, lo importarías en App.js o en otro componente y le pasarías un postId como prop:

// En App.js o donde lo necesites
// <PostDetail postId="1" /> 
// Asegúrate de que el ID exista en tu API
⚠️ Advertencia: Siempre maneja los estados `loading` y `error` en tus componentes. Esto mejora la experiencia de usuario y la robustez de tu aplicación.

🔄 Realizando Mutaciones (Mutations)

Las mutaciones son operaciones de GraphQL que modifican datos en el servidor, como crear, actualizar o eliminar registros. Para esto, Apollo Client nos proporciona el hook useMutation.

Ejemplo: Crear un Nuevo Post

Crearemos un componente CreatePostForm.js que permitirá a los usuarios añadir nuevos posts.

// components/CreatePostForm.js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, ActivityIndicator, Alert } from 'react-native';
import { gql, useMutation } from '@apollo/client';

const CREATE_POST_MUTATION = gql`
  mutation CreatePost($title: String!, $body: String!, $userId: ID!) {
    createPost(input: { title: $title, body: $body, userId: $userId }) {
      id
      title
      body
      user {
        id
        username
      }
    }
  }
`;

// También necesitarás definir cómo quieres actualizar el caché después de la mutación
// Por ejemplo, para que el nuevo post aparezca en la lista de GET_POSTS
const GET_POSTS = gql`
  query GetPosts {
    allPosts {
      id
      title
      body
      user {
        id
        username
      }
    }
  }
`;

const CreatePostForm = () => {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [userId, setUserId] = useState('1'); // Ejemplo: Hardcodeamos un userId

  const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
    // Opcional: Actualizar el caché para que la lista de posts se refresque
    update(cache, { data: { createPost } }) {
      const existingPosts = cache.readQuery({
        query: GET_POSTS,
      });

      if (existingPosts && existingPosts.allPosts) {
        cache.writeQuery({
          query: GET_POSTS,
          data: { allPosts: [createPost, ...existingPosts.allPosts] },
        });
      }
    },
    onCompleted: () => {
      Alert.alert('Éxito', 'Post creado correctamente!');
      setTitle('');
      setBody('');
    },
    onError: (err) => {
      Alert.alert('Error', `Fallo al crear el post: ${err.message}`);
    },
  });

  const handleSubmit = () => {
    if (!title.trim() || !body.trim() || !userId.trim()) {
      Alert.alert('Campos incompletos', 'Por favor, rellena todos los campos.');
      return;
    }
    createPost({ variables: { title, body, userId } });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.header}>Crear Nuevo Post</Text>
      <TextInput
        style={styles.input}
        placeholder="Título del Post"
        value={title}
        onChangeText={setTitle}
      />
      <TextInput
        style={[styles.input, styles.textArea]}
        placeholder="Contenido del Post"
        value={body}
        onChangeText={setBody}
        multiline
        numberOfLines={4}
      />
      <TextInput
        style={styles.input}
        placeholder="ID de Usuario (ej: 1)"
        keyboardType="numeric"
        value={userId}
        onChangeText={setUserId}
      />
      {loading ? (
        <ActivityIndicator size="small" color="#0000ff" />
      ) : (
        <Button title="Crear Post" onPress={handleSubmit} />
      )}
      {error && <Text style={styles.errorText}>Error: {error.message}</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    width: '90%',
    padding: 20,
    backgroundColor: '#f8f8f8',
    borderRadius: 10,
    marginBottom: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  header: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 15,
    textAlign: 'center',
  },
  input: {
    height: 45,
    borderColor: '#ddd',
    borderWidth: 1,
    borderRadius: 8,
    paddingHorizontal: 15,
    marginBottom: 15,
    fontSize: 16,
  },
  textArea: {
    height: 100,
    textAlignVertical: 'top',
    paddingTop: 10,
  },
  errorText: {
    color: 'red',
    marginTop: 10,
    textAlign: 'center',
  },
});

export default CreatePostForm;

Para usar este formulario, lo importarías en App.js:

// App.js
// ... otros imports ...
import CreatePostForm from './components/CreatePostForm';

export default function App() {
  return (
    <ApolloProvider client={client}>
      <View style={styles.container}>
        <Text style={styles.header}>🚀 Mi Aplicación GraphQL con React Native</Text>
        <CreatePostForm /> {/* Añade el formulario aquí */}
        <PostsList />
        <StatusBar style="auto" />
      </View>
    </ApolloProvider>
  );
}
// ... styles ...

🔄 Actualizando el Caché de Apollo (Importante)

Después de una mutación, a menudo querrás que los datos de tu UI se actualicen automáticamente. Apollo Client gestiona un caché normalizado (InMemoryCache). Cuando realizas una mutación, el caché no siempre sabe cómo los datos devueltos afectan a tus consultas existentes. Aquí es donde entra la función update en useMutation.

En el ejemplo anterior, update lee la consulta GET_POSTS del caché, añade el nuevo createPost al principio de la lista y luego escribe la lista actualizada de nuevo en el caché. Esto hace que cualquier componente que use GET_POSTS se re-renderice con el nuevo post.

Más sobre las estrategias de actualización del caché

Existen varias formas de actualizar el caché después de una mutación:

  1. update function: La más flexible. Te da control total sobre cómo el resultado de la mutación afecta el caché.
  2. refetchQueries: Puedes especificar una lista de consultas que deseas que Apollo refetch de la red después de la mutación. Es más simple pero puede ser menos eficiente ya que implica una nueva solicitud de red.
  3. optimisticResponse: Permite que tu UI se actualice inmediatamente con el resultado esperado de la mutación, incluso antes de que el servidor responda. Si el servidor devuelve un error, Apollo revertirá el estado del caché. Esto mejora drásticamente la percepción de velocidad en la UI.

Para optimisticResponse, tendrías que simular el objeto createPost que esperas que el servidor devuelva:

const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
  // ... update function ...
  optimisticResponse: {
    __typename: 'Mutation',
    createPost: {
      id: Math.random().toString(), // Un ID temporal
      title, // El título del formulario
      body, // El cuerpo del formulario
      user: { // Simula el objeto de usuario si es necesario
        __typename: 'User',
        id: userId,
        username: 'optimisticUser', // Nombre de usuario de ejemplo
      },
      __typename: 'Post',
    },
  },
  // ... onCompleted y onError ...
});

👂 Suscripciones (Subscriptions)

Las suscripciones son una característica de GraphQL que permite a los clientes recibir actualizaciones en tiempo real del servidor cuando ocurren ciertos eventos, como la creación de un nuevo post o un nuevo comentario. Esto se logra generalmente a través de WebSockets.

Para usar suscripciones con Apollo Client en React Native, necesitarás configurar un WebSocketLink.

⚙️ Configuración del WebSocketLink

Modifica tu archivo apollo.js para incluir un WebSocketLink. Necesitarás instalar una dependencia adicional:

npm install @apollo/client/link/ws @apollo/client/link/subscriptions subscriptions-transport-ws
# o yarn
yarn add @apollo/client/link/ws @apollo/client/link/subscriptions subscriptions-transport-ws

Ahora, actualiza apollo.js:

// apollo.js
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';
import { split } from '@apollo/client';

// 1. Configura el HTTP Link (para queries y mutations)
const httpLink = new HttpLink({
  uri: 'https://api.graphql.guide/graphql', // Reemplaza con la URL de tu API GraphQL HTTP
});

// 2. Configura el WebSocket Link (para subscriptions)
const wsLink = new WebSocketLink({
  uri: 'ws://api.graphql.guide/graphql', // Reemplaza con la URL de tu API GraphQL WebSocket
  options: {
    reconnect: true,
  },
});

// 3. Usa `split` para rutear operaciones a los links correctos
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

const client = new ApolloClient({
  link: splitLink, // Usa el link que divide las operaciones
  cache: new InMemoryCache(),
});

export default client;
🔥 Importante: La URL para `wsLink` debe ser un endpoint de WebSocket. Muchas APIs de ejemplo GraphQL *no* ofrecen un endpoint de suscripciones, o requiere una configuración específica del servidor (Apollo Server con `subscriptions-transport-ws` o `graphql-ws`). Si tu API de ejemplo no tiene un endpoint `ws://` funcional, no podrás probar las suscripciones directamente.

Ejemplo: Suscribirse a Nuevos Posts

Una vez configurado el WebSocketLink, puedes usar el hook useSubscription.

// components/NewPostsSubscription.js
import React, { useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { gql, useSubscription } from '@apollo/client';

const NEW_POST_SUBSCRIPTION = gql`
  subscription OnNewPost {
    newPost {
      id
      title
      body
      user {
        id
        username
      }
    }
  }
`;

const NewPostsSubscription = () => {
  const { data, loading, error } = useSubscription(NEW_POST_SUBSCRIPTION);

  useEffect(() => {
    if (data && data.newPost) {
      // Aquí puedes mostrar una notificación, añadir el post a una lista, etc.
      console.log('¡Nuevo post recibido en tiempo real!', data.newPost);
      // Por ejemplo, podrías mostrar un pequeño banner o toast
    }
  }, [data]);

  if (loading) return <Text style={styles.infoText}>Esperando nuevos posts...</Text>;
  if (error) return <Text style={styles.errorText}>Error en suscripción: {error.message}</Text>;

  if (data && data.newPost) {
    return (
      <View style={styles.notificationBox}>
        <Text style={styles.notificationText}>✨ Nuevo Post: "{data.newPost.title}"</Text>
        <Text style={styles.notificationAuthor}>por {data.newPost.user.username}</Text>
      </View>
    );
  }

  return null;
};

const styles = StyleSheet.create({
  infoText: {
    fontSize: 14,
    color: '#888',
    marginTop: 10,
    textAlign: 'center',
  },
  errorText: {
    color: 'red',
    fontSize: 14,
    marginTop: 10,
    textAlign: 'center',
  },
  notificationBox: {
    backgroundColor: '#e6ffed',
    padding: 10,
    borderRadius: 8,
    marginVertical: 10,
    borderColor: '#b2dfdb',
    borderWidth: 1,
  },
  notificationText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#388e3c',
  },
  notificationAuthor: {
    fontSize: 12,
    color: '#4caf50',
    marginTop: 2,
  },
});

export default NewPostsSubscription;

Integrar NewPostsSubscription en App.js:

// App.js
// ... otros imports ...
import NewPostsSubscription from './components/NewPostsSubscription';

export default function App() {
  return (
    <ApolloProvider client={client}>
      <View style={styles.container}>
        <Text style={styles.header}>🚀 Mi Aplicación GraphQL con React Native</Text>
        <CreatePostForm />
        <NewPostsSubscription /> {/* Añade el componente de suscripción */}
        <PostsList />
        <StatusBar style="auto" />
      </View>
    </ApolloProvider>
  );
}
// ... styles ...

Ahora, si tu backend GraphQL está configurado para emitir newPosts a través de WebSockets, tu aplicación React Native recibirá y mostrará estas actualizaciones en tiempo real.


📈 Gestión de Errores y Estados de Carga

Apollo Client facilita la gestión de los estados de carga (loading) y errores (error) a través de sus hooks. Siempre es crucial mostrarlos adecuadamente al usuario.

EstadoDescripciónAcción Recomendada
---------
loadingLa solicitud está en curso.Mostrar ActivityIndicator o un Skeleton UI.
errorOcurrió un error en la solicitud o en el servidor.Mostrar un mensaje de error claro al usuario, con opción de reintentar si aplica.
---------
dataLos datos se han recibido correctamente.Renderizar los datos.
💡 Consejo: Para errores más complejos, puedes usar la librería `react-error-boundary` para capturar errores de renderizado en tus componentes y mostrarlos de forma elegante.

📊 El Caché de Apollo: InMemoryCache

InMemoryCache es la implementación por defecto del caché de Apollo Client. Es un caché en memoria que normaliza tus datos GraphQL y los almacena en un formato de grafo. Esto significa que si pides el mismo objeto (ej. un Post con id: 1) en diferentes consultas, Apollo solo lo almacena una vez y lo reutiliza.

  • Normalización: Descompone objetos en el caché por su id (si está disponible en la respuesta del servidor), evitando duplicados.
  • Actualizaciones Reactivas: Cuando realizas una mutación y actualizas el caché, todos los componentes que escuchan esos datos se actualizan automáticamente.
  • Configuración: Puedes personalizar la forma en que el caché identifica los objetos (ej. typePolicies para tipos sin id o para fusiones personalizadas).
// apollo.js (ejemplo de typePolicies para personalizar el caché)
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink } from '@apollo/client';
// ... otros imports ...

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          allPosts: {
            keyArgs: false, // Indica que 'allPosts' no debe ser cacheada por argumentos específicos
            merge(existing = [], incoming) { // Cómo fusionar resultados de paginación, por ejemplo
              return [...existing, ...incoming];
            },
          },
        },
      },
      Post: {
        keyFields: ['id'], // Si tu campo de ID se llama diferente a 'id'
      },
    },
  }),
});

🛡️ Autenticación y Encabezados (Headers)

La mayoría de las aplicaciones reales requieren autenticación. Apollo Client te permite añadir tokens de autenticación (como JWT) a tus solicitudes a través de un ApolloLink.

npm install @apollo/client/link/context
# o yarn
yarn add @apollo/client/link/context

Actualiza apollo.js para incluir setContext:

// apollo.js
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context'; // Importa setContext
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';
import { split, from } from '@apollo/client'; // Importa 'from'
import AsyncStorage from '@react-native-async-storage/async-storage'; // Para almacenar el token

// 1. Configura el HTTP Link
const httpLink = new HttpLink({
  uri: 'https://api.graphql.guide/graphql',
});

// 2. Configura el WebSocket Link
const wsLink = new WebSocketLink({
  uri: 'ws://api.graphql.guide/graphql',
  options: {
    reconnect: true,
    connectionParams: async () => {
      const token = await AsyncStorage.getItem('token');
      return {
        authToken: token ? `Bearer ${token}` : '',
      };
    },
  },
});

// 3. Configura el Auth Link
const authLink = setContext(async (_, { headers }) => {
  const token = await AsyncStorage.getItem('token'); // Recupera el token del almacenamiento local
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});

// 4. Combina el authLink con el httpLink y el wsLink
const httpAuthLink = from([authLink, httpLink]); // Aplica authLink antes de httpLink

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpAuthLink, // Usa el link HTTP con autenticación
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

export default client;

Recuerda instalar @react-native-async-storage/async-storage:

npm install @react-native-async-storage/async-storage
# o yarn
yarn add @react-native-async-storage/async-storage

Ahora, cada solicitud (HTTP y WebSocket) enviará el token de autenticación si está presente en AsyncStorage. Deberías tener una lógica de inicio de sesión que guarde el token cuando el usuario se autentica.

Componente React Native useQuery | useMutation | useSubscription Apollo Client InMemoryCache (Persistencia local) Escribir Leer Apollo Link Chain AuthLink (Headers/JWT) HttpLink / WsLink (Transporte) Servidor GraphQL Llamada hook Datos/Carga Request Response

🌟 Consideraciones de Rendimiento y Optimización

  • Persistencia del Caché: Para una mejor experiencia de usuario, especialmente sin conexión o al reiniciar la aplicación, considera persistir el caché de Apollo en el almacenamiento local con librerías como apollo-cache-persist.
  • Fragmentos: Usa fragmentos GraphQL para reutilizar selecciones de campos entre diferentes consultas y mutaciones, manteniendo tu código más DRY (Don't Repeat Yourself).
  • Polling y Refetching: useQuery te permite configurar opciones como pollInterval para refetch automáticamente cada cierto tiempo, o refetch manual para actualizar datos bajo demanda.
  • Carga Lenta (Lazy Queries): Para consultas que no necesitas cargar al inicio del componente, usa useLazyQuery. Este hook te devuelve una función que puedes llamar para ejecutar la consulta cuando sea necesario (ej. al hacer clic en un botón).
// Ejemplo de useLazyQuery
import { useLazyQuery, gql } from '@apollo/client';

const GET_USER_PROFILE = gql`
  query GetUserProfile($userId: ID!) {
    user(id: $userId) {
      name
      email
      posts { title }
    }
  }
`;

const ProfileButton = ({ userId }) => {
  const [loadProfile, { loading, data, error }] = useLazyQuery(GET_USER_PROFILE);

  return (
    <View>
      <Button title="Cargar Perfil" onPress={() => loadProfile({ variables: { userId } })} />
      {loading && <Text>Cargando perfil...</Text>}
      {error && <Text>Error: {error.message}</Text>}
      {data && data.user && (
        <View>
          <Text>Nombre: {data.user.name}</Text>
          <Text>Email: {data.user.email}</Text>
        </View>
      )}
    </View>
  );
};

✅ Conclusión

Has completado un viaje exhaustivo a través de la integración de GraphQL en React Native utilizando Apollo Client. Ahora tienes las herramientas y el conocimiento para:

  • Configurar Apollo Client en tu proyecto React Native.
  • Realizar consultas para obtener datos.
  • Ejecutar mutaciones para modificar datos, con actualización de caché.
  • Configurar y usar suscripciones para actualizaciones en tiempo real.
  • Manejar estados de carga y errores.
  • Implementar autenticación en tus solicitudes GraphQL.

GraphQL ofrece una gran potencia y flexibilidad para el desarrollo de aplicaciones modernas. Al combinarlo con React Native y Apollo Client, estás construyendo aplicaciones que son no solo rápidas y eficientes, sino también escalables y fáciles de mantener.

¡Experimenta, construye y lleva tus habilidades de desarrollo móvil al siguiente nivel! 🚀

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!