Como persistir preferencias de usuario en localStorage desde JS en WordPress

Contents

Persistir preferencias de usuario en localStorage desde JavaScript en WordPress — guía completa

Este artículo explica en detalle cómo guardar y recuperar las preferencias de usuario en el navegador usando localStorage, integrarlo correctamente en WordPress y, opcionalmente, sincronizar esas preferencias con el servidor (user meta) para usuarios autenticados. Incluye consideraciones de seguridad, privacidad, degradación elegante y ejemplos completos de código listos para usar.

Por qué usar localStorage

  • Rápido: acceso sin round-trip al servidor, ideal para UI (modo oscuro, tamaño de fuente, diseños personalizados).
  • Persistente: los datos sobreviven al cierre del navegador (a diferencia de sessionStorage).
  • Independiente del servidor: útil para usuarios no autenticados.

Casos de uso típicos

  • Modo oscuro / claro.
  • Tamaño de fuente o tipografía preferida.
  • Estado de paneles (colapsados/expandidos), orden de columnas, filtros por defecto.
  • Preferencias de accesibilidad (contraste, espaciado).

Principios y buenas prácticas

  1. Namespace y versión: usa claves con prefijo del tema/plugin y versión, p. ej. wp_myplugin_prefs_v1. Facilita migraciones.
  2. Solo datos no sensibles: nunca almacenes contraseñas, tokens o datos personales sensibles en localStorage.
  3. Validación y saneamiento: valida y normaliza las preferencias al aplicarlas y antes de enviarlas al servidor.
  4. Degradación elegante: detecta ausencia de localStorage y usa cookies o defaults.
  5. Sincronización entre pestañas: usa el evento storage para sincronizar cambios en otras ventanas.
  6. Límites de tamaño: localStorage suele permitir varios MB por dominio mantén objetos ligeros (JSON pequeño).

Modelo de datos recomendado

Usa un objeto JSON con claves claras. Ejemplo:

  • darkMode: true/false
  • fontSize: smallmediumlarge
  • layout: gridlist

Clave sugerida: wp_theme_prefs_v1

Ejemplo: integrar todo en WordPress

1) Registrar y encolar el script desde functions.php (o plugin). Exponer el nonce y la ruta REST para sincronizar (solo para usuarios autenticados).

// functions.php (o archivo principal del plugin)
add_action(wp_enqueue_scripts, function() {
    wp_enqueue_script(
        user-prefs,
        get_template_directory_uri() . /js/user-prefs.js,
        array(), // dependencias si las hubiera
        filemtime( get_template_directory() . /js/user-prefs.js ),
        true
    )

    wp_localize_script(user-prefs, UserPrefsData, array(
        nonce    => wp_create_nonce(wp_rest),
        save_url => rest_url(user-prefs/v1/save),
        key      => wp_theme_prefs_v1,
        user_id  => get_current_user_id(),
    ))
})

// Registrar endpoint REST para guardar preferencias (solo para usuarios autenticados)
add_action(rest_api_init, function() {
    register_rest_route(user-prefs/v1, /save, array(
        methods  => POST,
        callback => my_user_prefs_save,
        permission_callback => function() {
            return is_user_logged_in()
        },
    ))
})

function my_user_prefs_save(WP_REST_Request request) {
    user_id = get_current_user_id()
    if (!user_id) {
        return new WP_Error(no_user, Usuario no autenticado, array(status => 401))
    }

    prefs = request->get_param(prefs)
    if (!is_array(prefs)) {
        return new WP_Error(invalid_data, Preferencias inválidas, array(status => 400))
    }

    // Sanitizar valores según necesidades
    allowed_fonts = array(small,medium,large)
    if (isset(prefs[fontSize])  !in_array(prefs[fontSize], allowed_fonts, true)) {
        prefs[fontSize] = medium
    }
    prefs[darkMode] = !empty(prefs[darkMode]) ? true : false

    update_user_meta(user_id, user_preferences, prefs)

    return rest_ensure_response(array(success => true, prefs => prefs))
}

2) Script JS que maneja lectura, escritura, sincronización local y opcionalmente envía al servidor.

// js/user-prefs.js
(function() {
  const KEY = (window.UserPrefsData  UserPrefsData.key)  wp_theme_prefs_v1
  const SAVE_URL = (window.UserPrefsData  UserPrefsData.save_url)  null
  const NONCE = (window.UserPrefsData  UserPrefsData.nonce)  null

  // Comprueba disponibilidad de localStorage
  function storageAvailable() {
    try {
      const test = __storage_test__
      localStorage.setItem(test, test)
      localStorage.removeItem(test)
      return true
    } catch (e) {
      return false
    }
  }

  // Fallback simple con cookies si no hay localStorage
  function setCookie(name, value, days = 365) {
    const expires = new Date(Date.now()   days864e5).toUTCString()
    document.cookie = name   =   encodeURIComponent(value)    expires=   expires    path=/
  }
  function getCookie(name) {
    return document.cookie.split( ).reduce((r, v) => {
      const parts = v.split(=)
      return parts[0] === name ? decodeURIComponent(parts[1]) : r
    }, )
  }

  function getPrefs() {
    if (storageAvailable()) {
      try {
        return JSON.parse(localStorage.getItem(KEY)  {})
      } catch (e) {
        return {}
      }
    } else {
      try {
        return JSON.parse(getCookie(KEY)  {})
      } catch (e) {
        return {}
      }
    }
  }

  function savePrefs(prefs) {
    const str = JSON.stringify(prefs)
    if (storageAvailable()) {
      localStorage.setItem(KEY, str)
    } else {
      setCookie(KEY, str)
    }
    // Opcional: sincronizar con servidor si es usuario autenticado y hay endpoint
    if (SAVE_URL  NONCE) {
      syncToServer(prefs)
    }
  }

  function syncToServer(prefs) {
    fetch(SAVE_URL, {
      method: POST,
      headers: {
        Content-Type: application/json,
        X-WP-Nonce: NONCE
      },
      credentials: same-origin,
      body: JSON.stringify({ prefs: prefs })
    }).then(res => {
      if (!res.ok) {
        // manejar error (registro, retries, etc.)
        return res.json().then(err => console.warn(Error guardando prefs en servidor:, err))
      }
      return res.json()
    }).catch(err => console.warn(Error fetch syncToServer:, err))
  }

  // Aplicar preferencias al DOM: ejemplo para modo oscuro y tamaño de fuente
  function applyPrefs(prefs) {
    // Dark mode
    if (prefs.darkMode) {
      document.documentElement.classList.add(pref-dark-mode)
    } else {
      document.documentElement.classList.remove(pref-dark-mode)
    }

    // Font size
    document.documentElement.classList.remove(pref-font-small,pref-font-medium,pref-font-large)
    const fs = prefs.fontSize  medium
    document.documentElement.classList.add(pref-font-   fs)

    // Puedes aplicar más preferencias aquí (layout, ocultar elementos, etc.)
  }

  // Enlazar controles del UI (ejemplo: checkbox y select)
  function bindControls() {
    const darkToggle = document.querySelector([data-pref=darkMode])
    const fontSelect = document.querySelector([data-pref=fontSize])

    if (darkToggle) {
      darkToggle.addEventListener(change, function() {
        const prefs = getPrefs()
        prefs.darkMode = darkToggle.checked
        savePrefs(prefs)
        applyPrefs(prefs)
      })
    }

    if (fontSelect) {
      fontSelect.addEventListener(change, function() {
        const prefs = getPrefs()
        prefs.fontSize = fontSelect.value
        savePrefs(prefs)
        applyPrefs(prefs)
      })
    }
  }

  // Sincroniza entre pestañas
  window.addEventListener(storage, function(e) {
    if (e.key === KEY) {
      try {
        const newPrefs = JSON.parse(e.newValue  {})
        applyPrefs(newPrefs)
      } catch (err) {
        // ignore
      }
    }
  })

  // Inicialización al cargar
  document.addEventListener(DOMContentLoaded, function() {
    const prefs = getPrefs()
    applyPrefs(prefs)
    bindControls()

    // Inicializar estados de controles si existen
    const darkToggle = document.querySelector([data-pref=darkMode])
    const fontSelect = document.querySelector([data-pref=fontSize])
    if (darkToggle) darkToggle.checked = !!prefs.darkMode
    if (fontSelect) fontSelect.value = prefs.fontSize  medium
  })

})()

Ejemplo de estilo CSS mínimo para aplicar las clases

/ styles.css (o inline) /
:root {
  --bg: #ffffff
  --text: #111
  --font-size: 16px
}
.pref-dark-mode {
  --bg: #111
  --text: #f5f5f5
}
.pref-font-small { font-size: 14px }
.pref-font-medium { font-size: 16px }
.pref-font-large { font-size: 18px }

body {
  background: var(--bg)
  color: var(--text)
  font-size: var(--font-size, 16px)
}

Migración de versiones de preferencias

Si cambias la estructura de tu objeto de preferencias, añade un campo version y una rutina de migración que al leer las prefs detecte versiones antiguas y las actualice al nuevo esquema. Ejemplo:

  • Si KEY es wp_theme_prefs_v1 y cambias a v2, lee v1, transforma y guarda en v2.
  • Puedes mantener ambas claves temporalmente para compatibilidad.

Privacidad, GDPR y consideraciones legales

  • localStorage puede almacenar identificadores que, combinados con otros datos, se consideran personales evalúa el impacto de privacidad.
  • Informar en la política de privacidad si guardas preferencias que pueden identificar a un usuario. Para preferencias no personales (modo visual), normalmente no es necesario consentimiento explícito, pero verifica la legislación aplicable.
  • Proporciona una forma para que el usuario borre sus preferencias (botón de reset) y, si sincronizas con el servidor, asegúrate de que también eliminas el user_meta en esa acción.

Problemas comunes y soluciones

  • localStorage bloqueado en modo incógnito: algunos navegadores limitan o lanzan excepciones captura errores y usa cookies como fallback.
  • Datos corruptos (JSON inválido): envolver parse en try/catch y restaurar valores por defecto si falla.
  • Sincronización entre pestañas no funcionando: asegurarse de que el cambio se hace con localStorage.setItem (no sessionStorage) y que las pestañas están en el mismo origen.

Checklist de implementación

  • Usar clave con namespace y versión.
  • Validar y sanitizar valores antes de aplicar.
  • Proveer fallback si localStorage no está disponible.
  • Si sincronizas al servidor: usar REST API, nonce y permission_callback apropiado.
  • Ofrecer botón para resetear preferencias.
  • Documentar en la política de privacidad el tratamiento de dichas preferencias.

Resumen final

Persistir preferencias en localStorage es una estrategia eficiente para mejorar la experiencia de usuario en WordPress. Combínala con una integración prudente en el backend (REST API user_meta) cuando necesites persistencia entre dispositivos para usuarios autenticados. Sigue las buenas prácticas de namespacing, validación, migración de versiones y respeto a la privacidad para asegurar una implementación robusta y escalable.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *