sábado, 21 de junio de 2025

Componente de React que guarda su valor cada vez que cambia, contra una API de Laravel

Para crear un componente React que envíe un mensaje a una API del servidor cada vez que detecta un cambio, vamos a utilizar el hook useEffect junto con el hook useState para manejar el estado del componente.

Aquí te presento un ejemplo de cómo podrías hacer este componente. Lo llamaremos AutoSaveInput para un campo de texto, pero la lógica es aplicable a cualquier tipo de cambio.


Componente React para Guardado Automático en la API

Este componente tendrá un campo de entrada de texto. Cada vez que el texto cambie y después de un breve retraso (para evitar enviar demasiadas solicitudes a la API con cada pulsación de tecla), se enviará el nuevo valor a tu servidor.

JavaScript
import React, { useState, useEffect } from 'react';

function AutoSaveInput({ initialValue = '', apiUrl, fieldName }) {
  // 1. Estado para almacenar el valor actual del input
  const [inputValue, setInputValue] = useState(initialValue);
  // 2. Estado para controlar si hay una operación de guardado en curso
  const [isSaving, setIsSaving] = useState(false);
  // 3. Estado para mostrar un mensaje de estado
  const [statusMessage, setStatusMessage] = useState('');

  // Efecto que se ejecuta cada vez que 'inputValue' cambia
  useEffect(() => {
    // Si el valor inicial es vacío y el input también está vacío, no hacemos nada al principio.
    // Esto evita enviar una solicitud al cargar el componente si no hay valor inicial.
    if (inputValue === initialValue && initialValue === '') {
      return;
    }

    setIsSaving(true);
    setStatusMessage('Guardando...');

    // Limpiamos cualquier temporizador anterior para evitar envíos múltiples
    // y solo enviamos la solicitud después de que el usuario deja de escribir por un momento.
    const debounceTimeout = setTimeout(() => {
      // Función para enviar los datos a la API
      const sendToApi = async () => {
        try {
          const response = await fetch(apiUrl, {
            method: 'POST', // O 'PUT'/'PATCH' dependiendo de tu API
            headers: {
              'Content-Type': 'application/json',
              'Accept': 'application/json',
              // Aquí podrías añadir tu token de autenticación si tu API lo requiere
              // 'Authorization': `Bearer TU_TOKEN_AQUI`,
              // Si usas Laravel Sanctum con cookies/CSRF:
              // 'X-XSRF-TOKEN': getCsrfTokenFromCookie(), // Necesitarías una función para obtenerlo
            },
            body: JSON.stringify({ [fieldName]: inputValue }), // Envía el campo y su valor
          });

          if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.message || 'Error al guardar');
          }

          setStatusMessage('Guardado exitoso!');
        } catch (error) {
          console.error('Error al enviar a la API:', error);
          setStatusMessage(`Error: ${error.message}`);
        } finally {
          setIsSaving(false);
          // Opcional: limpiar el mensaje de estado después de un tiempo
          setTimeout(() => setStatusMessage(''), 3000);
        }
      };

      sendToApi();
    }, 500); // Espera 500ms (0.5 segundos) después de la última pulsación

    // Función de limpieza para cancelar el temporizador si el componente se desmonta
    // o si inputValue cambia de nuevo antes de que el temporizador expire.
    return () => clearTimeout(debounceTimeout);

  }, [inputValue, apiUrl, fieldName, initialValue]); // Dependencias del efecto

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <label htmlFor={fieldName}>{fieldName}:</label>
      <input
        id={fieldName}
        type="text"
        value={inputValue}
        onChange={handleChange}
        disabled={isSaving} // Deshabilita el input mientras se guarda
        style={{ padding: '8px', border: '1px solid #ccc', borderRadius: '4px', marginRight: '10px' }}
      />
      {/* Indicador de estado */}
      {isSaving && <span style={{ color: 'blue' }}>Guardando...</span>}
      {statusMessage && !isSaving && (
        <span style={{ color: statusMessage.startsWith('Error') ? 'red' : 'green' }}>
          {statusMessage}
        </span>
      )}
    </div>
  );
}

export default AutoSaveInput;

Cómo usar este componente:

  1. Guarda el código anterior como AutoSaveInput.jsx (o .js) en tu carpeta de componentes.

  2. Importa y úsalo en tu página o componente:

    JavaScript
    import React from 'react';
    import AutoSaveInput from './AutoSaveInput'; // Ajusta la ruta según donde lo guardes
    
    function UserProfile() {
      const userId = 1; // ID del usuario actual o del recurso
      const userEmail = "usuario@ejemplo.com"; // Valor inicial del email
    
      // Supongamos que tu API para actualizar el email del usuario 1 es /api/users/1/email
      const emailApiUrl = `http://localhost:8000/api/users/${userId}/email`;
    
      return (
        <div>
          <h1>Perfil de Usuario</h1>
          <p>Este campo se guarda automáticamente al cambiar.</p>
    
          <AutoSaveInput
            initialValue={userEmail}
            apiUrl={emailApiUrl}
            fieldName="email"
          />
    
          <p>Otro campo:</p>
          <AutoSaveInput
            initialValue="Nombre de Ejemplo"
            apiUrl={`http://localhost:8000/api/users/${userId}/name`}
            fieldName="name"
          />
        </div>
      );
    }
    
    export default UserProfile;
    

Explicación del Código:

  1. useState(initialValue):

    • inputValue: Almacena el valor actual del campo de entrada. Cada vez que el usuario escribe, este estado se actualiza.
    • isSaving: Un booleano para indicar si hay una solicitud API en curso. Lo usamos para deshabilitar el input y mostrar un mensaje.
    • statusMessage: Un string para mostrar mensajes al usuario (ej. "Guardando...", "Guardado exitoso!", "Error: ...").
  2. useEffect(() => { ... }, [inputValue, apiUrl, fieldName, initialValue]):

    • Este es el corazón de la funcionalidad de "enviar al cambio". El efecto se re-ejecuta cada vez que inputValue (el texto en el input), apiUrl o fieldName cambian.
    • Debouncing (setTimeout y clearTimeout): Es crucial. Sin él, cada vez que el usuario pulsa una tecla, se enviaría una solicitud a la API. setTimeout espera 500ms (puedes ajustar este tiempo). Si inputValue cambia de nuevo antes de esos 500ms, el clearTimeout detiene el temporizador anterior, y se inicia uno nuevo. Esto asegura que la solicitud solo se envíe después de que el usuario ha dejado de escribir por un breve período.
    • Llamada a la API (fetch): Dentro del setTimeout, la función sendToApi realiza la petición fetch a la URL proporcionada.
      • method: 'POST' (o PUT/PATCH): El método HTTP que tu API de Laravel espera para actualizar un recurso.
      • headers: Son importantes para indicar que estás enviando JSON y, crucialmente, para la autenticación. Si usas Laravel Sanctum, deberías añadir el encabezado X-XSRF-TOKEN si estás en un dominio de primera parte (referencia: first-party domain) (como localhost:3000 a localhost:8000 con la configuración que vimos).
      • body: JSON.stringify({ [fieldName]: inputValue }): Envía los datos en formato JSON. Usamos [fieldName] para que el nombre de la propiedad en el JSON sea dinámico (ej. { "email": "nuevo@email.com" }).
    • Manejo de Respuestas y Errores: Se verifica response.ok para saber si la petición fue exitosa. Si no, se intenta leer el mensaje de error de la respuesta.
    • finally: Se usa para resetear isSaving sin importar si hubo éxito o error.
  3. handleChange:

    • Esta función es la típica para actualizar el estado inputValue cada vez que el contenido del campo de texto cambia.

Consideraciones Clave para tu Backend Laravel:

  • Endpoint de API: Asegúrate de que tengas una ruta en routes/api.php que pueda recibir peticiones POST, PUT o PATCH en la apiUrl que le pases al componente.
  • Controller de Laravel: Tu controlador debe estar preparado para recibir el JSON con el fieldName y su valor. Por ejemplo:
    PHP
    // En tu UserController.php o similar
    use Illuminate\Http\Request;
    use App\Models\User; // Asumiendo que User es tu modelo
    
    public function updateEmail(Request $request, User $user) // Asumiendo Route Model Binding para $user
    {
        $request->validate([
            'email' => 'required|email|max:255',
        ]);
    
        $user->email = $request->input('email');
        $user->save();
    
        return response()->json(['message' => 'Email actualizado correctamente']);
    }
    
    // En routes/api.php
    Route::post('/users/{user}/email', [UserController::class, 'updateEmail']);
    
  • Autenticación y CORS: Como ya hablamos, si tu frontend está en un puerto diferente (ej. localhost:3000 y localhost:8000), necesitarás la configuración de CORS con supports_credentials: true y la de Sanctum con SANCTUM_STATEFUL_DOMAINS para que las cookies de sesión/CSRF funcionen.
  • Protección CSRF (si aplicable): Si estás usando la autenticación de SPA de Sanctum con cookies, no olvides que tu frontend debe obtener el XSRF-TOKEN (haciendo una llamada a /sanctum/csrf-cookie) y luego enviar ese token en un encabezado X-XSRF-TOKEN en tus peticiones POST/PUT/PATCH/DELETE. Si usas Axios con withCredentials=true, a menudo esto se maneja automáticamente. Con fetch, tendrías que obtenerlo del document.cookie y añadirlo manualmente al encabezado.

Este componente te da una base sólida para implementar funcionalidad de guardado automático cada vez que un valor cambia. ¡Espero que te sea de gran ayuda!

No hay comentarios: