I have a main component, App(). I have another component, EventPage(). EventPage has a dialog, defined in a separate file, that opens and populates from a click on a table row. In that case, the data in the dialog can be modified and saved to a database.
The second case is to create a new event using the same dialog. The Add button for that is in another component, App(). When clicked, I want EventPage to open its dialog, un-populated.
I've seen many similar examples but I can't get them to work for my use case. I think I need to useContext but I don't get how to apply it. Here's some code to clarify:
index.tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
<App/>
);
App.tsx
const App = () => {
return (
<Router>
<MainAppBar isAppSidebarOpen={false} />
</Router>
);
};
The router goes to Event page from menu click in MainAppBar. A click on a table row opens and populates a MUI dialog. It will open just by setting the open property to true.
event.tsx
function handleRowClick(event: React.MouseEvent<unknown>, id: number) {
const selectedRowData = getSelectedRowData(id);
setDialogData(selectedRowData);
setOpen(true);
}
Now I need to open the same dialog, unpopulated, on a button click in the App component.
App.tsx
function ClickAddIconComponent(props: { key: string }) {
return (
<Fab size="small" color="primary" aria-label="add" onClick={handleAddIconClick} sx={{ marginTop: "6px", textAlign: "right"}}>
<AddIcon />
</Fab>
)
}
}
function handleAddIconClick() {
<TellEventPageToOpenDialog message="openEmptyDialog" />;
}
I'm assuming I need to useContext() to open Event's dialog?
interface MessageContextType {
message: string;
setMessage: React.Dispatch<React.SetStateAction<string>>;
}
const MessageContext = React.createContext<MessageContextType | null>(null);
const TellEventPageToOpenDialog = (props:any) => {
const [message, setMessage] = React.useState(props.message);
return (
<MessageContext.Provider value={{ message, setMessage }}>
<EventPage />
</MessageContext.Provider>
);
}
Now how do I get EventPage to receive receive the message (from App) and open its dialog? Am I going about this wrong?
EDIT: I don't want to render the dialog from App because it doesn't have access to all the many props that come from EventsPage, including callbacks. This is the call to show the dialog from EventsPage:
<EventDialog
open={open}
dialogData={dialogData}
slotProps={{
paper: {
component: 'form',
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const formJson = Object.fromEntries((formData).entries());
saveEvent(formJson)
addToTable(formData);
},
},
}}
isLoading={isLoading}
/>
You see that it includes many callbacks that are not available from App. That is why I need App to tell EventsPage to show the dialog.
I went to a producer/consumer route instead, where App is the producer and EventPage is the consumer. App produces a message when the Add button is clicked, while EventPage listens for that message and opens the dialog when it gets it. This pattern is facilitated by an EventBus, which I store in a separate file.
event-bus.tsx
class EventBus {
private listeners: { [key: string]: Function[] };
constructor() {
this.listeners = {};
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
off(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter(
(listener) => listener !== callback
);
}
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach((listener) => listener(data));
}
}
}
const eventBus = new EventBus();
export default eventBus;
App.tsx
import EventBus from './event-bus';
function handleAddIconClick() {
eventBus.emit('buttonClicked', { message: 'Show Dialog' });
}
events.tsx
import EventBus from "./event-bus";
useEffect(() => {
eventBus.on('buttonClicked', handleIncomingMessage);
return () => {
eventBus.off('buttonClicked', handleIncomingMessage);
};
}, [open]);
function handleIncomingMessage(data: any) {
setDialogData(initialDialogData);
setOpen(true);
}