Establecer el estado de un componente usando una función que se puede llamar desde cualquier lugar

Resuelto AndrewHoover898 asked hace 10 meses • 0 respuestas

Tengo un componente global llamado <MyGlobalComponent/>que reside en <App/>. Lo que estoy tratando de lograr es construir una función global showGlobalComponent()que pueda ser llamada desde cualquier otro componente, una que establezca su visibilidad y el contenido representado dentro de ella (que es otro componente).

MiComponenteGlobal.tsx

export type GlobalComponentProps {
  Component: React.FC
  isVisible: boolean
};

export function showGlobalComponent ({Component, isVisible}: GlobalComponentProps) {
  // ??? What do I do here ??? 
  // Supposedly I'd set isVisible & ComponentToRender state variables here, but I can't, I'm outside the function
}
    
function MyGlobalComponent() {
  const [ComponentToRender, setComponentToRender] = useState<React.FC | null>(null);
  const [isVisible, setIsVisible] = useState<boolean>(false)

  return (
    <View style={APPSTYLE.myComponentStyle} />
      {
        isVisible &&
        <>
         ComponentToRender
        </>
      } 
    </View>
  )

Aquí está el ejemplo de cómo lo llamaría.

OtroComponente.tsx

function AnotherComponent() {
  useEffect(() => {
    showGlobalComponent({
      isGlobalComponentVisible: true,
      Component => <TertiaryComponent myProp="asds"/>
    })
  }, [])

}

Pensé en usar Redux y solo memorizar una cadena asociada con el componente que quiero representar dentro de <MyGlobalComponent/> (because you cannot memoize non-serializable stuff with Redux). However, if I want to pass a prop to it like I did above to TertiaryComponent`, eso se vuelve bastante imposible.

Una solución que pensé (de la cual tengo dudas) es usar DeviceEventEmitterfrom react-nativepara emitir un evento y luego escucharlo dentro de un useEffectin<MyGlobalComponent/>

MiComponenteGlobal.tsx

export function showGlobalComponent (
  { Component, isVisible }: GlobalComponentProps
) {
  DeviceEventEmitter.emit("myEventName", Component, isVisible);
}

// ...
useEffect(() => {
  const myEvent = DeviceEventEmitter.addListener(
    "myEventName",
    ({ Component, isVisible }: GlobalComponentProps) => {
      setIsVisible(isVisible)
      setComponentToRender(Component) 
    }
  );
  return () => {
    myEvent.remove();
  };
}, []);

Sin embargo, el comportamiento de esto es inconsistente y no parece la forma correcta de hacerlo. Además, DeviceEventEmitterestá muy mal documentado. ¿Cuál es una forma más común o práctica de lograr esto?

AndrewHoover898 avatar Feb 15 '24 22:02 AndrewHoover898
Aceptado

Su código no reacciona muy correctamente. En lugar de declarar algo global sin restricciones y mutarlo localmente, debe crear un proveedor de contexto React para mantener el estado y proporcionar el componente y el valor de visibilidad a los consumidores.

Ejemplo:

MyGlobalComponentProvider.tsx

import {
  PropsWithChildren,
  ReactNode,
  Dispatch,
  createContext,
  useContext,
  useState
} from "react";

type GlobalComponentProps = {
  component: ReactNode;
  setComponent: Dispatch<ReactNode>;
  isVisible: boolean;
  setIsVisible: Dispatch<boolean>;
};

export const MyGlobalComponentContext = createContext<GlobalComponentProps>({
  component: null,
  setComponent: () => null,
  isVisible: false,
  setIsVisible: () => false
});

export const useMyGlobalComponentContext = () =>
  useContext(MyGlobalComponentContext);

export const MyGlobalComponent = () => {
  const { component, isVisible } = useMyGlobalComponentContext();

  return <div>{isVisible ? component : null}</div>;
};

const MyGlobalComponentProvider = ({ children }: PropsWithChildren<{}>) => {
  const [component, setComponent] = useState<ReactNode>(null);
  const [isVisible, setIsVisible] = useState(false);

  return (
    <MyGlobalComponentContext.Provider
      value={{
        component,
        isVisible,
        setComponent,
        setIsVisible
      }}
    >
      {children}
    </MyGlobalComponentContext.Provider>
  );
};

export default MyGlobalComponentProvider;

Importe el MyGlobalComponentProvidercomponente y ajuste la aplicación o el componente de nivel raíz para proporcionar el valor de contexto a ese sub-ReactTree. Los consumidores utilizan el gancho exportado useMyGlobalComponentContextpara acceder a los valores y procesarlos o procesarlos en consecuencia.

Aplicación

import MyGlobalComponentProvider, {
  MyGlobalComponent,
} from "./MyGlobalComponentProvider";

export default function App() {
  return (
    <MyGlobalComponentProvider>
      <div className="App">
        <MyGlobalComponent />
        <AnotherComponent />
      </div>
    </MyGlobalComponentProvider>
  );
}

OtroComponente.tsx

import { useEffect } from 'react';
import {
  useMyGlobalComponentContext
} from "./MyGlobalComponentProvider";

const AnotherComponent = () => {
  const { setComponent, setIsVisible } = useMyGlobalComponentContext();

  useEffect(() => {
    setComponent(<TertiaryComponent myProp="asds" />);
    setIsVisible(true);
  }, []);

  return <h1>AnotherComponent</h1>;
};

ingrese la descripción de la imagen aquí

Drew Reese avatar Feb 15 '2024 19:02 Drew Reese

Primero debe reaccionar.forwardRef a su componente global. Luego puede asignar funciones de mostrar/ocultar usando el gancho React.useImperativeHandle.

https://github.com/calintamas/react-native-toast-message/blob/main/src/Toast.tsx

Verifique esta implementación, podría ayudarlo a resolver el problema.

ko100v.d avatar Feb 15 '2024 16:02 ko100v.d

puede utilizar referencias y enlaces de manipulación de referencias como useImperativeHandle para este trabajo.

Preparé un ejemplo porque pensé que podría guiarte. Una forma adicional de comunicarse entre componentes es utilizar referencias globales.

Estos métodos se utilizan generalmente en mis propios proyectos, como Modal, Toast, etc., que se utilizarán en toda la aplicación. Lo uso para elementos. Aquí está el ejemplo;

import React, {useImperativeHandle, useState} from 'react';
import {Button, View} from 'react-native';

const GlobalComponentRef = React.createRef<GlobalComponentRef>();

const App = () => {
  return (
    <View>
      <GlobalComponent ref={GlobalComponentRef} />
      <AnotherComponent />
    </View>
  );
};

type GlobalComponentProps = {Component?: React.FC; visible?: boolean};
type GlobalComponentRef = {
  state: () => GlobalComponentProps;
  setComponent: (component: React.FC) => void;
  setVisible: (bool: boolean) => void;
};

const GlobalComponent = React.forwardRef<
  GlobalComponentRef,
  GlobalComponentProps
>(
  (
    {Component = React.Fragment, visible = false}: GlobalComponentProps,
    ref,
  ) => {
    const [_Component, setComponent] = useState<React.FC>(Component);
    const [_visible, setVisible] = useState<boolean>(visible);

    useImperativeHandle(
      ref,
      () => {
        return {
          state: () => ({visible: _visible, Component: _Component}),
          setComponent: (component: React.FC) => setComponent(component),
          setVisible: (bool: boolean) => setVisible(bool),
        };
      },
      [_Component, _visible],
    );

    return <View>{visible && <>ComponentToRender</>}</View>;
  },
);

const AnotherComponent = () => {
  const onPress = () => {
    const {visible} = GlobalComponentRef.current?.state() || {};
    GlobalComponentRef.current?.setVisible(!visible);
  };

  return (
    <View>
      <Button title={'Click Me'} onPress={onPress} />
    </View>
  );
};
HyopeR avatar Feb 15 '2024 16:02 HyopeR