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
First, we need to create a simple form component to encapsulate our logic. As you can see we set up the useState React Hook.
Please note the array destructuring [text, setText], where text is the stored value which we want to use, which in this case will be a string; and setText is a function used to update that value.
Create a new file TaskForm.jsx in your ui folder.
import React, { useState } from "react";
export const TaskForm = () => {
const [text, setText] = useState("");
return (
<form className="task-form">
<input type="text" placeholder="Type to add new tasks" />
<button type="submit">Add Task</button>
</form>
);
};3.2: Update the App component
Then we can simply add this to our App component above your list of tasks:
import React from "react";
import { useTracker, useSubscribe } from "meteor/react-meteor-data";
import { TasksCollection } from "/imports/api/TasksCollection";
import { Task } from "./Task";
import { TaskForm } from "./TaskForm";
export const App = () => {
const isLoading = useSubscribe("tasks");
const tasks = useTracker(() => TasksCollection.find({}).fetch());
if (isLoading()) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Welcome to Meteor!</h1>
<TaskForm />
<ul>
{tasks.map((task) => (
<Task key={task._id} task={task} />
))}
</ul>
</div>
);
};3.3: 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 className attribute in the form component.
.task-form {
margin-top: 1rem;
}3.4: 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.
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 and the main.jsx client file.
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "../imports/api/tasksCollection";
import "../imports/api/TasksPublications";
import "../imports/api/tasksMethods"; import React from "react";
import { createRoot } from "react-dom/client";
import { Meteor } from "meteor/meteor";
import { App } from "/imports/ui/App";
import "../imports/api/tasksMethods"; Now you can attach a submit handler to your form using the onSubmit event, and also plug your React Hook into the onChange event present in the input element.
As you can see you are using the useState React Hook to store the value of your <input> element. Note that you also need to set your value attribute to the text constant as well, this will allow the input element to stay in sync with our hook.
In more complex applications you might want to implement some
debounceorthrottlelogic if there are many calculations happening between potentially frequent events likeonChange. There are libraries which will help you with this, like Lodash, for instance.
import React, { useState } from "react";
export const TaskForm = () => {
const [text, setText] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
if (!text) return;
await Meteor.callAsync("tasks.insert", {
text: text.trim(),
createdAt: new Date(),
});
setText("");
};
return (
<form className="task-form" onSubmit={handleSubmit}>
<input
type="text"
placeholder="Type to add new tasks"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add Task</button>
</form>
);
};Inside the 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. We are also trimming the text to remove any extra spaces.
Also, insert a date createdAt in your task document so you know when each task was created.
3.5: 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.
..
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
..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.

