Establecer el estado de un componente usando una función que se puede llamar desde cualquier lugar
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 DeviceEventEmitter
from react-native
para emitir un evento y luego escucharlo dentro de un useEffect
in<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, DeviceEventEmitter
está muy mal documentado. ¿Cuál es una forma más común o práctica de lograr esto?
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 MyGlobalComponentProvider
componente 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 useMyGlobalComponentContext
para 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>;
};
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.
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>
);
};