Two ways to render content of a Modal

I was creating a Modal while developing a plugin and came across that there are two ways to render the content inside the modal.

First Method

I don’t know what this method is called, but I think obsidian provides some prebuilt functions to render each HTML element.

Example code :

import { App, Modal } from "obsidian";

export class Example extends Modal {
	constructor(app: App) {
		super(app);
	}
	onOpen() {
		const { contentEl } = this;
		contentEl.empty();

		const wrapper = contentEl.createEl('div', { cls: 'modal-content-wrapper' });

		// Title Input for Task
		const taskTitleLabel = wrapper.createEl('h6', { text: 'Task Title : ' });
		const taskTitleInput = wrapper.createEl('input', { type: 'text', placeholder: 'Enter task title' });
		taskTitleInput.style.marginBottom = '10px';

		const timeWrapper = wrapper.createEl('div', { cls: 'time-input-wrapper' });

		const startTimeWrapper = timeWrapper.createEl('div', { cls: 'start-time-input-wrapper' });
		const startTimeInputTitle = startTimeWrapper.createEl('h6', { text: 'Task Start Time :' });
		const startTimeInput = startTimeWrapper.createEl('input', { type: 'time' });
	}
	
	onClose(): void {
		this.contentEl.empty();
	}
}

Second Method

Use ReactDom package from react-dom/client and then normal HTML code.

Example Code :

import { App, Modal } from "obsidian";
import ReactDOM from "react-dom/client";

// Functional React component for the modal content
const ExampleContent: React.FC<{ app: App }> = () => {
	return (
		<div className="modal-content-wrapper">
			<label className="modal-content-wrapper-title-label">Task Title</label>
			<input type="text" className="modal-content-wrapper-title-input" value={title} onChange={(e) => setTitle(e.target.value)} />
			<div className="time-input-wrapper">
				<h6>Task Start Time :</h6>
				<input className="modal-content-wrapper-time-input" type="time" value={startTime} onChange={(e) => setStartTime(e.target.value)} />
			</div>
		</div>
	);
};

export class Example extends Modal {
	task: any;

	constructor(app: App) {
		super(app);
		this.app = app;
	}

	onOpen() {
		const { contentEl } = this;
		contentEl.empty();

		const container = document.createElement("div");
		contentEl.appendChild(container);

		const root = ReactDOM.createRoot(this.contentEl);

		root.render(<ExampleContent
			app={this.app}
		/>);
	}

	onClose() {
		const { contentEl } = this;
		contentEl.empty();
	}
}

So, I wanted to know, specially from the experts who are developing obsidian, which is the preferred way to create/render content inside a Modal. Or basically which will be an efficient way to achieve this task.
Of course, talking from the developer perspective, the Second method will be more preferred since we are familiar with that format and gives more power to create components and elements. But I want to know if there are any cons of using the second method, which might affect the performance or layout.

Also, want to know what exactly should be added in the onClose() function to clear out everything and close the Modal properly to free up the memory efficiently?

I’m going to move this to Devs: plugins and API.

Good luck!

Ohh yes, my bad, didn’t realize I posted this in Help. Thank you for that!

What differences might you expect to see? Which version do you find easier to use and understand?

I feel, if the first method is there in the first place, (provided by either Obsidian or Electron, I am not sure) there must be some reason it has been created that way, that is it either must be helping Obsidian to make the plugin code compatible with the main codebase, or it must be helping in optimum way to rendering the content, those HTML elements and everything.
I always write code with a mentality that, my code shouldn’t just work (which will always work😎), rather my code should work efficiently, taking as much less resources as possible.

Hence, just wanted to know if there are any pros of using the first method. Personally speaking, I am comfortable with the second method, as I have good experience in developing and maintaining frontEnd components and everything using HTML code.

Then I’d recommend not to use React. If you wanted a reactive framework, better to use Svelte which is far less resource intensive.

However if your needs are simple enough that you don’t need any framework, then just use the First Method from your OP.

1 Like

Ohh interesting ! I heard about Svelte long back but haven’t tried it, but since Obsidian supports it, I can try it out.
Thank you for answering the question and sharing the resources.

1 Like

You can use whatever javascript approach you’d like, whether its plain javascript, or a framework like React or Svelte. I wouldn’t worry so much about optimization when you’re starting out, so focus on using the tool or tools that you’re familiar with and continue from there.

In terms of performance: your two examples are essentially equivalent. They are both constructing elements using javascript and applying them to the DOM. You can argue that React is slower because it has a higher upfront cost; It needs to do a lot more work before it can start creating elements and pushing them to the screen (setting up the React runtime, building the virtual dom, etc.) but at the end of the day, it doesn’t really matter.

1 Like

root.unmount() should be called when closing :stuck_out_tongue:

1 Like

Got it ! Actually I came across the first method, so just presumed that it might have been developed for some reason, or recommended over the normal approach of using plain JavaScript or using any framework. But personally I feel comfortable with React, as it’s better to understand as well as there is more freedom in terms of whatever idea you want to build with it. I’ll be also going through the Svelte and might migrate to that framework.

Anyway, thank you so much @liam for the insights, helps me to understand things better.

Okay, i thought the below two lines will just empty the content Element i had created. I guess i can use the unmount function before calling the empty function. Thank you for mentioning that.

		const { contentEl } = this;
		contentEl.empty();