Files
meteor/v3-docs/docs/tutorials/solid/3.forms-and-events.md
2025-10-28 10:40:16 -03:00

7.4 KiB
Raw Blame History

3: Forms and Events

All apps need to allow the user to perform some sort of interaction with the data that is stored. In our case, the first type of interaction is to insert new tasks. Without it, our To-Do app wouldn't be very helpful.

One of the main ways in which a user can insert or edit data on a website is through forms. In most cases, it is a good idea to use the <form> tag since it gives semantic meaning to the elements inside it.

3.1: Create Task Form

Create a new form inside the App.jsx file, and inside well add an input field and a button. Place it between the header and the Show elements:

::: code-group

...
     </header>

      <form class="task-form" onSubmit={addTask}>
        <input
          type="text"
          placeholder="Type to add new tasks"
          value={newTask()}
          onInput={(e) => setNewTask(e.currentTarget.value)}
        />
        <button type="submit">Add Task</button>
      </form>

      <Show
...

:::

In the code above, we've integrated the form directly into the App.jsx component, positioning it above the task list. We're using Solid's signals for two-way binding on the input (via value and onInput) and an onSubmit handler for form submission.

Let's now add our signals and addTask() function handler inside the top of the App function definition. It will call a Meteor method tasks.insert that isn't yet created but we'll soon make:

::: code-group

...

export const App = () => {
  const [newTask, setNewTask] = createSignal('');

  const addTask = async (event) => {
    event.preventDefault();
    if (newTask().trim()) {
      await Meteor.callAsync("tasks.insert", {
        text: newTask(),
        createdAt: new Date(),
      });
      setNewTask('');
    }
  };

  const subscription = Meteor.subscribe("tasks");

...

:::

Altogether, our file should look like:

::: code-group

import { createSignal, For, Show } from "solid-js";
import { Meteor } from "meteor/meteor";
import { Tracker } from "meteor/tracker";
import { TasksCollection } from "../api/TasksCollection";

export const App = () => {
  const [newTask, setNewTask] = createSignal('');

  const addTask = async (event) => {
    event.preventDefault();
    if (newTask().trim()) {
      await Meteor.callAsync("tasks.insert", {
        text: newTask(),
        createdAt: new Date(),
      });
      setNewTask('');
    }
  };

  const subscription = Meteor.subscribe("tasks");
  const [isReady, setIsReady] = createSignal(subscription.ready());
  const [tasks, setTasks] = createSignal([]);

  Tracker.autorun(async () => {
    setIsReady(subscription.ready());
    setTasks(await TasksCollection.find().fetchAsync());
  });

  return (
    <div class="container">
      <header>
        <h1>Todo List</h1>
      </header>

      <form class="task-form" onSubmit={addTask}>
        <input
          type="text"
          placeholder="Type to add new tasks"
          value={newTask()}
          onInput={(e) => setNewTask(e.currentTarget.value)}
        />
        <button type="submit">Add Task</button>
      </form>

      <Show
        when={isReady()}
        fallback={<div>Loading ...</div>}
      >
        <ul>
          <For each={tasks()}>
            {(task) => (
              <li>{task.text}</li>
            )}
          </For>
        </ul>
      </Show>
    </div>
  );
};

:::

3.2: Update the Stylesheet

You also can style it as you wish. For now, we only need some margin at the top so the form doesn't seem off the mark. Add the CSS class .task-form, this needs to be the same name in your class attribute in the form element. Notice how in Solid you don't use className like you would in React.

::: code-group

...

.task-form {
  margin-top: 1rem;
}

...

:::

3.3: Add Submit Handler

Now let's create a function to handle the form submit and insert a new task into the database. To do it, we will need to implement a Meteor Method.

Methods are essentially RPC calls to the server that let you perform operations on the server side securely. You can read more about Meteor Methods here.

To create your methods, you can create a file called TasksMethods.js.

::: code-group

import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";

Meteor.methods({
  "tasks.insert"(doc) {
    return TasksCollection.insertAsync(doc);
  },
});

:::

Remember to import your method on the main.js server file, delete the insertTask function, and invoke the new meteor method inside the forEach block.

::: code-group

import { Meteor } from "meteor/meteor";
import { TasksCollection } from "/imports/api/TasksCollection";
import "../imports/api/TasksPublications";
import "../imports/api/TasksMethods"; // [!code highlight]

Meteor.startup(async () => {
  if ((await TasksCollection.find().countAsync()) === 0) {
    [
      "First Task",
      "Second Task",
      "Third Task",
      "Fourth Task",
      "Fifth Task",
      "Sixth Task",
      "Seventh Task",
    ].forEach((taskName) => {
      Meteor.callAsync("tasks.insert", { // [!code highlight]
        text: taskName, // [!code highlight]
        createdAt: new Date(), // [!code highlight]
      });      
    });
  }
});

:::

Inside the forEach function, we are adding a task to the tasks collection by calling Meteor.callAsync(). The first argument is the name of the method we want to call, and the second argument is the text of the task.

Also, insert a date createdAt in your task document so you know when each task was created.

Now we need to import TasksMethods.js in a client-side file to enable optimistic UI (e.g., in imports/ui/main.jsx):

::: code-group

/* @refresh reload */
import { render } from 'solid-js/web';
import { App } from './App';
import { Meteor } from "meteor/meteor";
import './main.css';
import "/imports/api/TasksMethods"; // [!code highlight] // this import allows for optimistic execution

Meteor.startup(() => {
  render(() => <App/>, document.getElementById('root'));
});

:::

In the addTask function (shown in 3.1), we prevent the default form submission, get the input value, call the Meteor method to insert the task optimistically, and clear the input.

Meteor methods execute optimistically on the client using MiniMongo while simultaneously calling the server. If the server call fails, MiniMongo rolls back the change, providing a speedy user experience. It's a bit like rollback netcode in fighting video games.

3.4: Show Newest Tasks First

Now you just need to make a change that will make users happy: we need to show the newest tasks first. We can accomplish this quite quickly by sorting our Mongo query.

::: code-group

...

Tracker.autorun(async () => {
  setIsReady(subscription.ready());
  setTasks(await TasksCollection.find({}, { sort: { createdAt: -1 } }).fetchAsync()); // [!code highlight]
});

...

:::

Your app should look like this:

In the next step, we are going to update your tasks state and provide a way for users to remove tasks.