Source: templates/LoadAsync.js

import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { connect, useSelector } from "react-redux";
import { FormContext, emptyObject, processIncludeVariables, processOptions, useIncludeVariables } from "../..";
import { appActions } from "../../redux";
import { Template } from "./";
import { useSpinnerContext, booleanString } from "../..";

const msSpinnerTimeout = window.config.CONSTANTS.MS_SPINNER_TIMEOUT || 500;

/**
 * Componente que renderiza un template asincrónicamente, y lo actualiza cada cierto tiempo.
 * @name Reactor.Components.Templates.LoadAsync
 * @param {string} href Nombre de la operación que se va a llamar
 * @param {JsonString?} data Datos que se quieran pasar a la operación
 * @param {BooleanString} [include_form = "false"] Indica si se incluyen en la data una propiedad form con los datos del form que contiene el loadasync
 * @param {string} auto_refresh Se puede establecer un intérvalo en ms para realizar un refresco automático
 * @param {string} hook Gancho para refrescar el componente con un CallerAsync (si se usa hook auto_refresh no tiene efecto)
 * @param {BooleanString} [use_token = "false"] Si es "true" envía el token del flujo actual a la operación 
 * @param {BooleanString} [load_on_render = "true"] Trae los datos en el primer renderizado, sin esperar el auto_refresh
 * @param {BooleanString} [use_spinner = "false"] Indica si se muestra un spinner mientras se espera la respuesta del servidor
 * @param {BooleanString} [debug = "false"] Envía informacion a la consola para depurar el componente
 * @param {BooleanString} [refresh_on_change_data = "false"] Hace una llamada cada vez que alguno de los datos obtenidos de "data", "include_variables" o "include_form" se modifica
 * @param {string} include_variables Lista de variables que se incluyen en data (separadas por espacio)
 * @param {string} context Contexto contenedor de las variables que se van a incluir, es requerido si se incluyen variables
 * @class
 * @example
<load_async href="HomeMovimientos" data='{"idProducto":{{idProducto}}}' auto_refresh="{{refreshRate}}"/>
<load_async href="HomeMovimientos" data='{"idProducto":{{idProducto}}}' hook="NombreDelGancho" use_token="true" use_form="true"/>
 */


const _LoadAsync = ({ 
  href, 
  data = false, 
  include_form = "false", 
  auto_refresh, 
  hook, 
  use_token = "false", 
  load_on_render = "true", 
  use_spinner = "false", 
  debug = "false",
  refresh_on_change_data = "false",
  include_variables, 
  context,
  getAsyncContentUntoken, 
  loadAsyncHookRegister, 
  children, 
}) => {


  const { formInputData } = useContext(FormContext);
  const inputRef = useRef();
  
  const hookState = useSelector(state => (hook && state.app.hooks[hook]) || hookStateDefault);
  const [ loaded, setLoaded ] = useState();
  const [ loaded2, setLoaded2 ] = useState(false);
  const spinner = useSpinnerContext();
  const [ content, setContent ] = useState();
  const variablesState = useSelector(state => context === "_" ? state.app.variables : state.app.variables[context] ) || null;
  

  useEffect(()=>{
    setTimeout(() => {
      setLoaded2(true);
    }, 10); 
  }, [])

  useEffect(()=>{
    if(hook){
      loadAsyncHookRegister(hook);
    }
  }, [hook])

  useEffect(()=>{
    if(booleanString(load_on_render) && !loaded){
      if (booleanString(debug)) {
        console.log({event: "load on render", hookState, hook, load_on_render, loaded});
      } 
      setLoaded(true)
      loadData(hookState);
    }
  }, [hookState, load_on_render, hook, loaded])

  useEffect(()=>{
    if(loaded && hookState?.id !== null){
      if (booleanString(debug)) {
        console.log({event: "hook update", hookState, hook, loaded});
      } 
      loadData(hookState);
    }
  }, [hookState, hook, loaded])

  useEffect(()=>{
    if (booleanString(debug)) {
      console.log({auto_refresh, hook});
    } 
    if (auto_refresh !== undefined && auto_refresh !== 0 && auto_refresh !== "0"){
      const timer = setInterval(()=>loadData(), auto_refresh * 1);
      return () => clearInterval(timer);
    }
  }, [hook, auto_refresh])

  const token = useSelector(state => state.app.token) || null;

  const variablesData = useIncludeVariables({variablesState, include_variables});  
  const formData = useMemo(() => (include_form === "true") ? {form: formInputData} : emptyObject, [formInputData, include_form]);
  const formDataStr = useMemo(() => JSON.stringify(formData), [formData]);
  
  const sendData = useMemo(() => ({...processOptions(data), ...JSON.parse(formDataStr), ...variablesData}), [data, formDataStr, variablesData]);
  
  useEffect(() => {
    if(loaded2 && booleanString(refresh_on_change_data)){
      if (booleanString(debug)) {
        console.log({event: "hook update change data", sendData});
      } 
      loadData();
    }
  }, [refresh_on_change_data, sendData, loaded2])

  const loadData = useCallback( ({silent, noisy} = {}) => {
    const useSpinner = silent ? false : (
                        noisy ? true : (
                          use_spinner === "true"
                        )
                      );
    const spinnerTimer = useSpinner && setTimeout(() => spinner.show(), msSpinnerTimeout);
    getAsyncContentUntoken(href, sendData, use_token === "true" ? token : null)
      .then(response => {
        if (booleanString(debug)) {
          console.log({response});
        } 
        setContent(response.data.data);
      })
      .catch(reason => 
        console.warn(reason)
      )
      .finally(()=>{
        clearTimeout(spinnerTimer);
        useSpinner && spinner.hide();
      });

  }, [use_spinner, href, use_token, token, sendData]);

  return (
    <>
      {
        include_form === "true" && 
        <input type="hidden" ref={inputRef} />
      }
      {content && <Template content={content} ></Template>}
      {children}
    </>
  )
}

const mapStateToProps = (state) => {
  return emptyObject;
}

const hookStateDefault = {id: null, noisy: false, silent: false};

export const LoadAsync = connect(mapStateToProps, appActions)(_LoadAsync)