Merge pull request #13269 from meteor/feature/react-tutorial

React tutorial with Meteor 3
This commit is contained in:
Denilson
2024-09-03 09:25:11 -04:00
committed by GitHub
28 changed files with 1658 additions and 62 deletions

View File

@@ -7,16 +7,17 @@ export default defineConfig({
head: [
["link", { rel: "icon", href: "/logo.png" }],
[
'script',
"script",
{
async: '',
src: 'https://widget.kapa.ai/kapa-widget.bundle.js',
async: "",
src: "https://widget.kapa.ai/kapa-widget.bundle.js",
"data-website-id": "64051b0e-d79f-4fe7-b3ca-ff5c84075693",
"data-project-name": "Meteor",
"data-project-color": "#36436b",
"data-project-logo": "https://v3-docs.meteor.com/logo.png",
"data-modal-disclaimer": "This is a custom LLM for answering questions about Meteor. Answers are based on the contents of the docs, answered forum posts, YouTube videos and GitHub issues. Please note that answers are generated by AI and may not be fully accurate, so please use your best judgement."
}
"data-modal-disclaimer":
"This is a custom LLM for answering questions about Meteor. Answers are based on the contents of the docs, answered forum posts, YouTube videos and GitHub issues. Please note that answers are generated by AI and may not be fully accurate, so please use your best judgement.",
},
],
],
lastUpdated: true,
@@ -27,81 +28,102 @@ export default defineConfig({
// https://vitepress.dev/reference/default-theme-config
nav: [
{
text: 'Docs',
text: "Docs",
activeMatch: `^/(guide|docs|examples)/`,
items: [
{ text: 'Quick Start', link: '/about/install' },
{ text: 'Examples', link: 'https://github.com/meteor/examples' },
{ text: "Quick Start", link: "/about/install" },
{ text: "Examples", link: "https://github.com/meteor/examples" },
{
text: 'Meteor.js 2 Docs',
link: 'https://v2-docs.meteor.com'
text: "Meteor.js 2 Docs",
link: "https://v2-docs.meteor.com",
},
{
text: 'Migration from Meteor.js 2',
link: 'https://v3-migration-docs.meteor.com'
text: "Migration from Meteor.js 2",
link: "https://v3-migration-docs.meteor.com",
},
{
text: 'Tutorials',
text: "Tutorials",
items: [
{ text: 'Meteor + Vue + vue-meteor-tracker', link: '/tutorials/meteorjs3-vue3-vue-meteor-tracker' },
]
{
text: "Meteor.js 3 + React",
link: "/tutorials/react/index",
},
{
text: "Meteor + Vue + vue-meteor-tracker",
link: "/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker",
},
],
},
]
],
},
{
text: 'Ecosystem',
text: "Ecosystem",
activeMatch: `^/ecosystem/`,
items: [
{
text: 'Community & Help',
text: "Community & Help",
items: [
{
text: 'Meteor Forums',
link: 'https://forums.meteor.com'
text: "Meteor Forums",
link: "https://forums.meteor.com",
},
{
text: 'Meteor Lounge Discord',
link: 'https://discord.gg/hZkTCaVjmT'
text: "Meteor Lounge Discord",
link: "https://discord.gg/hZkTCaVjmT",
},
{
text: 'GitHub Discussions',
link: 'https://github.com/meteor/meteor/discussions'
text: "GitHub Discussions",
link: "https://github.com/meteor/meteor/discussions",
},
]
],
},
{
text: 'Resources',
items: [
{ text: 'Packages on Atmosphere', link: 'https://atmospherejs.com/' },
{ text: 'VS Code Extension', link: 'https://marketplace.visualstudio.com/items?itemName=meteor-toolbox.meteor-toolbox' },
{ text: 'DevTools - Chrome Extension', link: 'https://chromewebstore.google.com/detail/ibniinmoafhgbifjojidlagmggecmpgf' },
{ text: 'DevTools - Firefox Extension', link: 'https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/' },
]
},
{
text: 'Learning',
text: "Resources",
items: [
{
text: 'Meteor University',
link: 'https://university.meteor.com'
text: "Packages on Atmosphere",
link: "https://atmospherejs.com/",
},
{
text: 'Youtube Channel',
link: 'https://www.youtube.com/@meteorsoftware'
text: "VS Code Extension",
link: "https://marketplace.visualstudio.com/items?itemName=meteor-toolbox.meteor-toolbox",
},
]
{
text: "DevTools - Chrome Extension",
link: "https://chromewebstore.google.com/detail/ibniinmoafhgbifjojidlagmggecmpgf",
},
{
text: "DevTools - Firefox Extension",
link: "https://addons.mozilla.org/en-US/firefox/addon/meteor-devtools-evolved/",
},
],
},
{
text: 'News',
text: "Learning",
items: [
{ text: 'Blog on Dev.to', link: 'https://dev.to/meteor' },
{ text: 'Blog on Medium', link: 'https://blog.meteor.com' },
{ text: 'Twitter', link: 'https://x.com/meteorjs' },
{ text: 'LinkedIn', link: 'https://www.linkedin.com/company/meteor-software/' },
]
}
]
{
text: "Meteor University",
link: "https://university.meteor.com",
},
{
text: "Youtube Channel",
link: "https://www.youtube.com/@meteorsoftware",
},
],
},
{
text: "News",
items: [
{ text: "Blog on Dev.to", link: "https://dev.to/meteor" },
{ text: "Blog on Medium", link: "https://blog.meteor.com" },
{ text: "Twitter", link: "https://x.com/meteorjs" },
{
text: "LinkedIn",
link: "https://www.linkedin.com/company/meteor-software/",
},
],
},
],
},
{ text: "API", link: "/api/" },
{ text: "Galaxy Cloud", link: "https://www.meteor.com/cloud" },
@@ -219,9 +241,9 @@ export default defineConfig({
link: "/api/package",
},
{
text: 'Top Level Await',
link: '/api/top-level-await'
}
text: "Top Level Await",
link: "/api/top-level-await",
},
],
collapsed: true,
},
@@ -358,7 +380,14 @@ export default defineConfig({
{
text: "Tutorials",
items: [
{ link: "/tutorials/meteorjs3-vue3-vue-meteor-tracker", text: "Meteor + Vue + vue-meteor-tracker" },
{
text: "Meteor.js 3 + React",
link: "/tutorials/react/index",
},
{
link: "/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker",
text: "Meteor + Vue + vue-meteor-tracker",
},
],
collapsed: true,
},
@@ -381,23 +410,23 @@ export default defineConfig({
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/meteor/meteor' },
{ icon: 'twitter', link: 'https://x.com/meteorjs' },
{ icon: 'discord', link: 'https://discord.gg/hZkTCaVjmT' }
{ icon: "github", link: "https://github.com/meteor/meteor" },
{ icon: "twitter", link: "https://x.com/meteorjs" },
{ icon: "discord", link: "https://discord.gg/hZkTCaVjmT" },
],
logo: { dark: "/meteor-logo.png", light: "/meteor-blue.png" },
search: {
provider: 'algolia',
provider: "algolia",
options: {
appId: '2RBX3PR26I',
apiKey: '7fcba92008b84946f04369df2afa1744',
indexName: 'meteor_docs_v3',
appId: "2RBX3PR26I",
apiKey: "7fcba92008b84946f04369df2afa1744",
indexName: "meteor_docs_v3",
searchParameters: {
facetFilters: ["lang:en"],
},
}
},
},
footer: {

View File

@@ -150,4 +150,4 @@
### [webapp](https://github.com/meteor/meteor/tree/devel/packages/webapp) {#webapp}
### [webapp-hashing](https://github.com/meteor/meteor/tree/devel/packages/webapp-hashing) {#webapp-hashing}
### [weibo-config-ui](https://github.com/meteor/meteor/tree/devel/packages/weibo-config-ui) {#weibo-config-ui}
### [weibo-oauth](https://github.com/meteor/meteor/tree/devel/packages/weibo-oauth) {#weibo-oauth}
### [weibo-oauth](https://github.com/meteor/meteor/tree/devel/packages/weibo-oauth) {#weibo-oauth}

View File

@@ -0,0 +1,120 @@
## 1: Creating the app
### 1.1: Install Meteor {#install-meteor}
First, we need to install Meteor.
If you don't have Meteor installed, you can install it by running:
```shell
npx meteor
```
### 1.2: Create Meteor Project {#create-meteor-project}
The easiest way to setup Meteor with React is by using the command `meteor create` with the option `--react` and your project name (you can also omit the `--react` option since it is the default):
```shell
meteor create simple-todos-react
```
Meteor will create all the necessary files for you.
The files located in the `client` directory are setting up your client side (web), you can see for example `client/main.jsx` where Meteor is rendering your App main component into the HTML.
Also, check the `server` directory where Meteor is setting up the server side (Node.js), you can see the `server/main.js` is initializing your MongoDB database with some data. You don't need to install MongoDB as Meteor provides an embedded version of it ready for you to use.
You can now run your Meteor app using:
```shell
meteor run
```
Don't worry, Meteor will keep your app in sync with all your changes from now on.
Your React code will be located inside the `imports/ui` directory, and `App.jsx` file is the root component of your React To-do app.
Take a quick look at all the files created by Meteor, you don't need to understand them now but it's good to know where they are.
### 1.3: Create Task Component {#create-task-component}
You will make your first change now. Create a new file called `Task.jsx` in your `ui` folder.
This file will export a React component called `Task` that will represent one task in your To-Do list.
::: code-group
```js [imports/ui/Task.jsx]
import React from "react";
export const Task = ({ task }) => {
return <li>{task.text}</li>;
};
```
:::
As this component will be inside a list you are returning a `li` element.
### 1.4: Create Sample Tasks {#create-sample-tasks}
As you are not connecting to your server and your database yet let's define some sample data which will be used shortly to render a list of tasks. It will be an array, and you can call it `tasks`.
::: code-group
```js [imports/ui/App.jsx]
import React from 'react';
const tasks = [
{_id: 1, text: 'First Task'},
{_id: 2, text: 'Second Task'},
{_id: 3, text: 'Third Task'},
];
export const App = () => ...
```
:::
You can put anything as your `text` property on each task. Be creative!
### 1.5: Render Sample Tasks {#render-sample-tasks}
Now we can implement some simple rendering logic with React. We can now use our previous `Task` component to render our list items.
In React you can use `{` `}` to write Javascript code between them.
See below that you will use a `.map` function from the `Array` object to iterate over your sample tasks.
::: code-group
```js [imports/ui/App.jsx]
import React from 'react';
import { Task } from './Task';
const tasks = ..;
export const App = () => (
<div>
<h1>Welcome to Meteor!</h1>
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task }/>) }
</ul>
</div>
);
```
:::
Remember to add the `key` property to your task, otherwise React will emit a warning because it will see many components of the same type as siblings. Without a key, it will be hard for React to re-render one of them if necessary.
> You can read more about React and Keys [here](https://reactjs.org/docs/lists-and-keys.html#keys).
Remove the `Hello` and `Info` from your `App` component, remember to also remove the imports for them at the top of the file. Remove the `Hello.jsx` and `Info.jsx` files as well.
### 1.6: Hot Module Replacement {#hot-module-replacement}
Meteor by default when using React is already adding for you a package called `hot-module-replacement`. This package updates the javascript modules in a running app that were modified during a rebuild. Reduces the feedback cycle while developing so you can view and test changes quicker (it even updates the app before the build has finished). You are also not going to lose the state, your app code will be updated and your state will be the same.
In the next step, we are going to work with our MongoDB database to store our tasks.

View File

@@ -0,0 +1,191 @@
## 2: Collections
Meteor already sets up MongoDB for you. In order to use our database, we need to create a _collection_, which is where we will store our _documents_, in our case our `tasks`.
> You can read more about collections [here](https://v3-docs.meteor.com/api/collections.html).
In this step, we will implement all the necessary code to have a basic collection for our tasks up and running using React hooks.
### 2.1: Create Tasks Collection {#create-tasks-collection}
We can create a new collection to store our tasks by creating a new file at `imports/api/TasksCollection.js` which instantiates a new Mongo collection and exports it.
::: code-group
```js [imports/api/TasksCollection.js]
import { Mongo } from "meteor/mongo";
export const TasksCollection = new Mongo.Collection("tasks");
```
:::
Notice that we stored the file in the `imports/api` directory, which is a place to store API-related code, like publications and methods. You can name this folder as you want, this is just a choice.
You can delete the `links.js` file in this folder as we are not going to use this collection.
> You can read more about app structure and imports/exports [here](http://guide.meteor.com/structure.html).
### 2.2: Initialize Tasks Collection {#initialize-tasks-collection}
For our collection to work, you need to import it in the server so it sets some plumbing up.
You can either use `import "/imports/api/TasksCollection"` or `import { TasksCollection } from "/imports/api/TasksCollection"` if you are going to use on the same file, but make sure it is imported.
Now it is easy to check if there is data or not in our collection, otherwise, we can insert some sample data easily as well.
You don't need to keep the old content of `server/main.js`.
::: code-group
```js [server/main.js]
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "/imports/api/TasksCollection";
const insertTask = (taskText) =>
TasksCollection.insertAsync({ text: taskText });
Meteor.startup(async () => {
if ((await TasksCollection.find().countAsync()) === 0) {
[
"First Task",
"Second Task",
"Third Task",
"Fourth Task",
"Fifth Task",
"Sixth Task",
"Seventh Task",
].forEach(insertTask);
}
});
```
:::
So you are importing the `TasksCollection` and adding a few tasks to it iterating over an array of strings and for each string calling a function to insert this string as our `text` field in our `task` document.
### 2.3: Render Tasks Collection {#render-tasks-collection}
Now comes the fun part, you will render the tasks using a React Function Component and a Hook called `useTracker` from a package called [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data).
> Meteor works with Meteor packages and NPM packages, usually, Meteor packages are using Meteor internals or other Meteor packages.
This package is already included in the React skeleton (`meteor create yourproject`) so you don't need to add it but you can always add Meteor packages running `meteor add package-name`:
```shell
meteor add react-meteor-data
```
Now you are ready to import code from this package, when importing code from a Meteor package the only difference from NPM modules is that you need to prepend `meteor/` in the from part of your import.
The `useTracker` function exported by `react-meteor-data` is a React Hook that allows you to have reactivity in your React components. Every time the data changes through reactivity your component will re-render. Cool, right?
> For more information about React Hooks read [here](https://reactjs.org/docs/hooks-faq.html).
::: code-group
```javascript [imports/ui/App.jsx]
import React from "react";
import { useTracker } from "meteor/react-meteor-data";
import { TasksCollection } from "/imports/api/TasksCollection";
import { Task } from "./Task";
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}).fetch());
return (
<div>
<h1>Welcome to Meteor!</h1>
<ul>
{tasks.map((task) => (
<Task key={task._id} task={task} />
))}
</ul>
</div>
);
};
```
:::
But wait! Something is missing. If you run your app now, you'll see that you don't render any tasks.
That's because we need to publish our data to the client.
Fist, create a publication for our tasks:
`imports/api/TasksPublications.js`
```javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";
Meteor.publish("tasks", () => {
return TasksCollection.find();
});
```
Now, we need to import this file in our server:
::: code-group
```js [server/main.js]
...
import { TasksCollection } from '/imports/api/TasksCollection';
import "../imports/api/TasksPublications"; // [!code highlight]
const insertTask = taskText => TasksCollection.insertAsync({ text: taskText });
...
```
:::
The only thing left is subscribe to this publication:
`imports/ui/App.jsx`
```javascript
import React from 'react';
import { useTracker, useSubscribe } from 'meteor/react-meteor-data'; // [!code highlight]
import { TasksCollection } from '/imports/api/TasksCollection';
import { Task } from './Task';
export const App = () => {
const isLoading = useSubscribe("tasks"); // [!code highlight]
const tasks = useTracker(() => TasksCollection.find({}).fetch());
if (isLoading()) {
return <div>Loading...</div>;
}
...
}
```
As you can see, when subscribing to a publication using `useSubscribe` you'll get a `isLoading` function, that you can use to render some loading component before the data is ready.
> For more information on Publications/Subscriptions, please check our [docs](https://v3-docs.meteor.com/api/meteor.html#pubsub).
See how your app should look like now:
<img width="200px" src="/tutorials/react/assets/collections-tasks-list.png"/>
You can change your data on MongoDB in the server and your app will react and re-render for you.
You can connect to your MongoDB running `meteor mongo` in the terminal from your app folder or using a Mongo UI client, like [NoSQLBooster](https://nosqlbooster.com/downloads). Your embedded MongoDB is running in port `3001`.
See how to connect:
<img width="500px" src="/tutorials/react/assets/collections-connect-db.png"/>
See your database:
<img width="500px" src="/tutorials/react/assets/collections-see-database.png"/>
You can double-click your collection to see the documents stored on it:
<img width="500px" src="/tutorials/react/assets/collections-documents.png"/>
In the next step, we are going to create tasks using a form.

View File

@@ -0,0 +1,203 @@
## 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.
::: code-group
```js [imports/ui/TaskForm.jsx]
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:
::: code-group
```js [imports/ui/App.jsx]
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.
::: code-group
```css [client/main.css]
.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](https://guide.meteor.com/methods.html).
To create your methods, you can create a file called `tasksMethods.js`.
::: code-group
```javascript [imports/api/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.
::: code-group
```javascript [server/main.js]
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "../imports/api/tasksCollection";
import "../imports/api/TasksPublications";
import "../imports/api/tasksMethods"; // [!code highlight]
```
```javascript [client/main.jsx]
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"; // [!code highlight]
```
:::
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 `debounce` or `throttle` logic if there are many calculations happening between potentially frequent events like `onChange`. There are libraries which will help you with this, like [Lodash](https://lodash.com/), for instance.
::: code-group
```js [imports/ui/TaskForm.jsx]
import React, { useState } from "react";
import { TasksCollection } from "/imports/api/TasksCollection";
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](https://guide.meteor.com/collections.html#mongo-collections) query.
::: code-group
```js [imports/ui/App.jsx]
..
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
..
```
:::
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step03-form-new-task.png"/>
<img width="200px" src="/tutorials/react/assets/step03-new-task-on-list.png"/>
In the next step, we are going to update your tasks state and provide a way for users to remove tasks.

View File

@@ -0,0 +1,155 @@
## 4: Update and Remove
Up until now, you have only inserted documents into our collection. Let's take a look at how you can update and remove them by interacting with the user interface.
### 4.1: Add Checkbox
First, you need to add a `checkbox` element to your `Task` component.
> Be sure to add the `readOnly` attribute since we are not using `onChange` to update the state.
>
> We also have to force our `checked` prop to a `boolean` since React understands that an `undefined` value as inexistent, therefore causing the component to switch from uncontrolled to a controlled one.
>
> You are also invited to experiment and see how the app behaves for learning purposes.
You also want to receive a callback, a function that will be called when the checkbox is clicked.
::: code-group
```js [imports/ui/Task.jsx]
import React from "react";
export const Task = ({ task, onCheckboxClick }) => {
return (
<li>
<input
type="checkbox"
checked={!!task.isChecked}
onClick={() => onCheckboxClick(task)}
readOnly
/>
<span>{task.text}</span>
</li>
);
};
```
:::
### 4.2: Toggle Checkbox
Now you can update your task document by toggling its `isChecked` field.
First, create a new method called `tasks.toggleChecked` to update the `isChecked` property.
::: code-group
```javascript [imports/api/tasksMethods.js]
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";
Meteor.methods({
..
"tasks.toggleChecked"({ _id, isChecked }) {
return TasksCollection.updateAsync(_id, {
$set: { isChecked: !isChecked },
});
},
});
```
:::
Now, create a function to change your document and pass it along to your `Task` component.
::: code-group
```js [imports/ui/App.jsx]
..
export const App = () => {
const handleToggleChecked = ({ _id, isChecked }) =>
Meteor.callAsync("tasks.toggleChecked", { _id, isChecked });
..
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={handleToggleChecked} />) }
</ul>
..
```
:::
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step04-checkbox.png"/>
### 4.3: Remove tasks
You can remove tasks with just a few lines of code.
First, add a button after text in your `Task` component and receive a callback function.
::: code-group
```js [imports/ui/Task.jsx]
import React from 'react';
export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
return (
..
<span>{task.text}</span>
<button onClick={ () => onDeleteClick(task) }>&times;</button>
..
```
:::
Now add the removal logic in the `App`, you need to have a function to delete the task and provide this function in your callback property in the `Task` component.
For that, let's create a new method called `tasks.delete`:
::: code-group
```javascript [imports/api/tasksMethods.js]
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";
Meteor.methods({
..
"tasks.delete"({ _id }) {
return TasksCollection.removeAsync(_id);
},
});
```
:::
Then, let's call this method inside a `handleDelete` function:
::: code-group
```js [imports/ui/App.jsx]
export const App = () => {
..
const handleDelete = ({ _id }) =>// [!code highlight]
Meteor.callAsync("tasks.delete", { _id });// [!code highlight]
..
<ul>
{ tasks.map(task => <Task
key={ task._id }
task={ task }
onCheckboxClick={handleToggleChecked}
onDeleteClick={handleDelete} // [!code highlight]
/>) }
</ul>
..
}
```
:::
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step04-delete-button.png"/>
In the next step, we are going to improve the look of your app using CSS with Flexbox.

View File

@@ -0,0 +1,209 @@
## 5: Styles
### 5.1: CSS
Our user interface up until this point has looked quite ugly. Let's add some basic styling which will serve as the foundation for a more professional looking app.
Replace the content of our `client/main.css` file with the one below, the idea is to have an app bar at the top, and a scrollable content including:
- form to add new tasks;
- list of tasks.
::: code-group
```css [client/main.css]
body {
font-family: sans-serif;
background-color: #315481;
background-image: linear-gradient(to bottom, #315481, #918e82 100%);
background-attachment: fixed;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
font-size: 14px;
}
button {
font-weight: bold;
font-size: 1em;
border: none;
color: white;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
padding: 5px;
cursor: pointer;
}
button:focus {
outline: 0;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
}
.app-header {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.main {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: auto;
background: white;
}
.main::-webkit-scrollbar {
width: 0;
height: 0;
background: inherit;
}
header {
background: #d2edf4;
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
}
.app-bar {
display: flex;
justify-content: space-between;
}
.app-bar h1 {
font-size: 1.5em;
margin: 0;
display: inline-block;
margin-right: 1em;
}
.task-form {
display: flex;
margin: 16px;
}
.task-form > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
}
.task-form > input:focus {
outline: 0;
}
.task-form > button {
min-width: 100px;
height: 95%;
background-color: #315481;
}
.tasks {
list-style-type: none;
padding-inline-start: 0;
padding-left: 16px;
padding-right: 16px;
margin-block-start: 0;
margin-block-end: 0;
}
.tasks > li {
display: flex;
padding: 16px;
border-bottom: #eee solid 1px;
align-items: center;
}
.tasks > li > span {
flex-grow: 1;
}
.tasks > li > button {
justify-self: flex-end;
background-color: #ff3046;
}
```
:::
> If you want to learn more about this stylesheet check this article about [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/), and also this free [video tutorial](https://flexbox.io/) about it from [Wes Bos](https://twitter.com/wesbos).
>
> Flexbox is an excellent tool to distribute and align elements in your UI.
### 5.2: Applying styles
Now you need to add some elements around your components. You are going to add a `className` to your main div in the `App`, also a `header` element with a few `divs` around your `h1`, and a main `div` around your form and list. Check below how it should be, pay attention to the name of the classes, they need to be the same as in the CSS file:
::: code-group
```js [imports/ui/App.jsx]
..
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>Welcome to Meteor!</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<ul className="tasks">
{tasks.map((task) => (
<Task
key={task._id}
task={task}
onCheckboxClick={handleToggleChecked}
onDeleteClick={handleDelete}
/>
))}
</ul>
</div>
</div>
);
```
:::
> In React we use `className` instead of `class` as React uses Javascript to define the UI and `class` is a reserved word in Javascript.
Also, choose a better title for your app, Meteor is amazing but you don't want to see `Welcome to Meteor!` in your app top bar all the time.
You could choose something like:
::: code-group
```js [imports/ui/App.jsx]
..
<h1>📝️ To Do List</h1>
..
```
:::
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step05-styles.png"/>
In the next step, we are going to make this task list more interactive, for example, providing a way to filter tasks.

View File

@@ -0,0 +1,126 @@
## 6: Filter tasks
In this step, you will filter your tasks by status and show the number of pending tasks.
### 6.1: useState
First, you are going to add a button to show or hide the completed tasks from the list.
The `useState` function from React is the best way to keep the state of this button. It returns an array with two items, where the first element is the value of the state, and the second is a setter function that is how you are going to update your state. You can use _array destructuring_ to get these two back and already declare a variable for them.
Bear in mind that the names used for the constants do not belong to the React API, you can name them whatever you like.
Also, add a `button` below the task form that will display a different text based on the current state.
::: code-group
```js [imports/ui/App.jsx]
import React, { useState } from 'react';
..
export const App = () => {
const [hideCompleted, setHideCompleted] = useState(false);
..
<div className="main">
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
..
```
:::
You can read more about the `useState` hook [here](https://react.dev/reference/react/useState).
We recommend that you add your hooks always in the top of your components, so it will be easier to avoid some problems, like always running them in the same order.
### 6.2: Button style
You should add some style to your button so it does not look gray and without a good contrast. You can use the styles below as a reference:
::: code-group
```css [client/main.css]
.filter {
display: flex;
justify-content: center;
}
.filter > button {
background-color: #62807e;
}
```
:::
### 6.3: Filter Tasks
Now, if the user wants to see only pending tasks you can add a filter to your selector in the Mini Mongo query, you want to get all the tasks that are not `isChecked` true.
::: code-group
```js [imports/ui/App.jsx]
..
const hideCompletedFilter = { isChecked: { $ne: true } };
const tasks = useTracker(() =>
TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
sort: { createdAt: -1 },
}).fetch()
);
..
```
:::
### 6.4: Meteor Dev Tools Extension
You can install an extension to visualize the data in your Mini Mongo.
[Meteor DevTools Evolved](https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf) will help you to debug your app as you can see what data is on Mini Mongo.
<img width="800px" src="/tutorials/react/assets/step06-extension.png"/>
You can also see all the messages that Meteor is sending and receiving from the server, this is useful for you to learn more about how Meteor works.
<img width="800px" src="/tutorials/react/assets/step06-ddp-messages.png"/>
Install it in your Google Chrome browser using this [link](https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf).
### 6.5: Pending tasks
Update the App component in order to show the number of pending tasks in the app bar.
You should avoid adding zero to your app bar when there are no pending tasks.
::: code-group
```js [imports/ui/App.jsx]
..
const pendingTasksCount = useTracker(() =>
TasksCollection.find(hideCompletedFilter).count()
);
const pendingTasksTitle = `${
pendingTasksCount ? ` (${pendingTasksCount})` : ''
}`;
..
<h1>
📝️ To Do List
{pendingTasksTitle}
</h1>
..
```
:::
You could do both finds in the same `useTracker` and then return an object with both properties but to have a code that is easier to understand, we created two different trackers here.
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step06-all.png"/>
<img width="200px" src="/tutorials/react/assets/step06-filtered.png"/>
In the next step we are going to include user access in your app.

View File

@@ -0,0 +1,425 @@
## 7: Adding User Accounts
### 7.1: Password Authentication
Meteor already comes with a basic authentication and account management system out of the box, so you only need to add the `accounts-password` to enable username and password authentication:
```shell
meteor add accounts-password
```
> There are many more authentication methods supported. You can read more about the accounts system [here](https://v3-docs.meteor.com/api/accounts.html).
We also recommend you to install `bcrypt` node module, otherwise, you are going to see a warning saying that you are using a pure-Javascript implementation of it.
```shell
meteor npm install --save bcrypt
```
> You should always use `meteor npm` instead of only `npm` so you always use the `npm` version pinned by Meteor, this helps you to avoid problems due to different versions of npm installing different modules.
### 7.2: Create User Account
Now you can create a default user for our app, we are going to use `meteorite` as username, we just create a new user on server startup if we didn't find it in the database.
::: code-group
```js [server/main.js]
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/api/TasksCollection';
..
const SEED_USERNAME = 'meteorite';
const SEED_PASSWORD = 'password';
Meteor.startup(async () => {
if (!(await Accounts.findUserByUsername(SEED_USERNAME))) {
await Accounts.createUser({
username: SEED_USERNAME,
password: SEED_PASSWORD,
});
}
..
});
```
:::
You should not see anything different in your app UI yet.
### 7.3: Login Form
You need to provide a way for the users to input the credentials and authenticate, for that we need a form.
We can implement it using `useState` hook. Create a new file called `LoginForm.jsx` and add a form to it. You should use `Meteor.loginWithPassword(username, password);` to authenticate your user with the provided inputs.
::: code-group
```js [imports/ui/LoginForm.jsx]
import { Meteor } from "meteor/meteor";
import React, { useState } from "react";
export const LoginForm = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const submit = (e) => {
e.preventDefault();
Meteor.loginWithPassword(username, password);
};
return (
<form onSubmit={submit} className="login-form">
<label htmlFor="username">Username</label>
<input
type="text"
placeholder="Username"
name="username"
required
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor="password">Password</label>
<input
type="password"
placeholder="Password"
name="password"
required
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Log In</button>
</form>
);
};
```
:::
Ok, now you have a form, let's use it.
### 7.4: Require Authentication
Our app should only allow an authenticated user to access its task management features.
We can accomplish that by returning the `LoginForm` component when we don't have an authenticated user, otherwise we return the form, filter, and list component.
You should first wrap the 3 components (form, filter, and list) in a `<Fragment>`, Fragment is a special component in React that you can use to group components together without affecting your final DOM, it means without affecting your UI as it is not going to introduce other elements in the HTML.
> Read more about Fragments [here](https://react.dev/reference/react/Fragment)
So you can get your authenticated user or null from `Meteor.user()`, you should wrap it in a `useTracker` hook for it to be reactive. Then you can return the `Fragment` with Tasks and everything else or `LoginForm` based on the user being present or not in the session.
::: code-group
```js [imports/ui/App.jsx]
import { Meteor } from 'meteor/meteor';
import React, { useState, Fragment } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { TasksCollection } from '/imports/api/TasksCollection';
import { Task } from './Task';
import { TaskForm } from './TaskForm';
import { LoginForm } from './LoginForm';
..
export const App = () => {
const user = useTracker(() => Meteor.user());
..
return (
..
<div className="main">
{user ? (
<Fragment>
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
<ul className="tasks">
{tasks.map(task => (
<Task
key={task._id}
task={task}
onCheckboxClick={handleToggleChecked}
onDeleteClick={handleDelete}
/>
))}
</ul>
</Fragment>
) : (
<LoginForm />
)}
</div>
..
```
:::
### 7.5: Login Form style
Ok, let's style the login form now:
Wrap your pairs of label and input in `div`s so it will easier to control it on CSS. Do the same to the button tag.
::: code-group
```jsx [imports/ui/LoginForm.jsx]
<form onSubmit={submit} className="login-form">
<div>
<label htmlFor="username">Username</label>
<input
type="text"
placeholder="Username"
name="username"
required
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
placeholder="Password"
name="password"
required
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<button type="submit">Log In</button>
</div>
</form>
```
:::
And then update the CSS:
::: code-group
```css [client/main.css]
.login-form {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
}
.login-form > div {
margin: 8px;
}
.login-form > div > label {
font-weight: bold;
}
.login-form > div > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
margin-top: 4px;
}
.login-form > div > input:focus {
outline: 0;
}
.login-form > div > button {
background-color: #62807e;
}
```
:::
Now your login form should be centralized and beautiful.
### 7.6: Server startup
Every task should have an owner from now on. So go to your database, as you learn before, and remove all the tasks from there:
`db.tasks.remove({});`
Change your `server/main.js` to add the seed tasks using your `meteorite` user as owner.
Make sure you restart the server after this change so `Meteor.startup` block will run again. This is probably going to happen automatically anyway as you are going to make changes in the server side code.
::: code-group
```js [server/main.js]
import { Meteor } from "meteor/meteor";
import { Accounts } from "meteor/accounts-base";
import { TasksCollection } from "/imports/api/TasksCollection";
const insertTask = (taskText, user) =>
TasksCollection.insert({
text: taskText,
userId: user._id,
createdAt: new Date(),
});
const SEED_USERNAME = "meteorite";
const SEED_PASSWORD = "password";
Meteor.startup(async () => {
if (!(await Accounts.findUserByUsername(SEED_USERNAME))) {
await Accounts.createUser({
username: SEED_USERNAME,
password: SEED_PASSWORD,
});
}
const user = await Accounts.findUserByUsername(SEED_USERNAME);
if ((await TasksCollection.find().countAsync()) === 0) {
[
"First Task",
"Second Task",
"Third Task",
"Fourth Task",
"Fifth Task",
"Sixth Task",
"Seventh Task",
].forEach((taskText) => insertTask(taskText, user));
}
});
```
:::
See that we are using a new field called `userId` with our user `_id` field, we are also setting `createdAt` field.
### 7.7: Task owner
First, let's change our publication to publish the tasks only for the currently logged user. This is important for security, as you send only data that belongs to that user.
::: code-group
```js [/imports/api/TasksPublications.js]
Meteor.publish("tasks", function () {
const userId = this.userId;
if (!userId) {
return this.ready();
}
return TasksCollection.find({ userId });
});
```
:::
Now let's check if we have a `user` before trying to fetch any data:
::: code-group
```js [imports/ui/App.jsx]
..
const tasks = useTracker(() => {
if (!user) {
return [];
}
return TasksCollection.find(
hideCompleted ? hideCompletedFilter : {},
{
sort: { createdAt: -1 },
}
).fetch();
});
const pendingTasksCount = useTracker(() => {
if (!user) {
return 0;
}
..
});
..
```
:::
Also, update the `tasks.insert` method to include the field `userId` when creating a new task:
::: code-group
```js [imports/api/tasksMethods.js]
..
Meteor.methods({
"tasks.insert"(doc) {
return TasksCollection.insertAsync({
...doc,
userId: this.userId,
});
},
..
```
:::
### 7.8: Log out
We also can better organize our tasks by showing the username of the owner below our app bar. You can include a new `div` right after our `Fragment` start tag.
On this, you can add an `onClick` handler to logout the user as well. It is very straightforward, just call `Meteor.logout()` on it.
::: code-group
```js [imports/ui/App.jsx]
..
const logout = () => Meteor.logout();
return (
..
<Fragment>
<div className="user" onClick={logout}>
{user.username} 🚪
</div>
..
```
:::
Remember to style your username as well.
::: code-group
```css [client/main.css]
.user {
display: flex;
align-self: flex-end;
margin: 8px 16px 0;
font-weight: bold;
cursor: pointer;
}
```
:::
Phew! You have done quite a lot in this step. Authenticated the user, set the user in the tasks, and provided a way for the user to log out.
Your app should look like this:
<img width="200px" src="/tutorials/react/assets/step07-login.png"/>
<img width="200px" src="/tutorials/react/assets/step07-logout.png"/>
In the next step, we are going to learn how to deploy your app!

View File

@@ -0,0 +1,98 @@
## 8: Deploying
Deploying a Meteor application is similar to deploying any other Node.js app that uses websockets. You can find deployment options in [our guide](https://guide.meteor.com/deployment), including Meteor Up, Docker, and our recommended method, Galaxy.
In this tutorial, we will deploy our app on [Galaxy](https://www.meteor.com/hosting), which is our own cloud solution. Galaxy offers a free plan, so you can deploy and test your app. Pretty cool, right?
### 8.1: Create your account
You need a Meteor account to deploy your apps. If you dont have one yet, you can [sign up here](https://cloud.meteor.com/?isSignUp=true).
With this account, you can access our package manager, [Atmosphere](https://atmospherejs.com/), [Forums](https://forums.meteor.com/) and more.
### 8.2: Set up MongoDB (Optional)
As your app uses MongoDB the first step is to set up a MongoDB database, Galaxy offers MongoDB hosting on a free plan for testing purposes, and you can also request for a production ready database that allows you to scale.
In any MongoDB provider you will have a MongoDB URL which you must use it. If you use the free option provided by Galaxy, the initial setup is done for you.
Galaxy MongoDB URL will be like this: `mongodb://username:<password>@org-dbname-01.mongodb.galaxy-cloud.io` .
> You can read more about Galaxy MongoDB [here](https://galaxy-guide.meteor.com/galaxy-database-mongodb-general) and general MongoDB set up [here](https://galaxy-guide.meteor.com/mongodb.html).
### 8.3: Set up settings
You need to create a setting file, its a JSON file that Meteor apps can read configurations from. Create this file in a new folder called `private` in the root of your project. It is important to notice that `private` is a special folder that is not going to be published to the client side of your app.
Make sure you replace `Your MongoDB URL` by your own MongoDB URL :)
::: code-group
```json [private/settings.json]
{
"galaxy.meteor.com": {
"env": {
"MONGO_URL": "Your MongoDB URL"
}
}
}
```
:::
### 8.4: Deploy it
Now you are ready to deploy, run `meteor npm install` before deploying to make sure all your dependencies are installed.
You also need to choose a subdomain to publish your app. We are going to use the main domain `meteorapp.com` that is free and included on any Galaxy plan.
In this example we are going to use `react-meteor-3.meteorapp.com` but make sure you select a different one, otherwise you are going to receive an error.
> You can learn how to use custom domains on Galaxy [here](https://galaxy-guide.meteor.com/custom-domains.html). Custom domains are available starting with the Essentials plan.
Run the deployment command:
```shell
meteor deploy react-meteor-3.meteorapp.com --free --mongo
```
> If you are not using the free hosting with MongoDB on Galaxy, then remove the `--mongo` flag from the deploy script and add `--settings private/settings.json` with the proper setting for your app.
Make sure you replace `react-meteor-3` by a custom name that you want as subdomain. You will see a log like this:
```shell
meteor deploy react-meteor-3.meteorapp.com --settings private/settings.json
Talking to Galaxy servers at https://us-east-1.galaxy-deploy.meteor.com
Preparing to build your app...
Preparing to upload your app...
Uploaded app bundle for new app at vue-tutorial.meteorapp.com.
Galaxy is building the app into a native image.
Waiting for deployment updates from Galaxy...
Building app image...
Deploying app...
You have successfully deployed the first version of your app.
For details, visit https://galaxy.meteor.com/app/react-meteor-3.meteorapp.com
```
This process usually takes just a few minutes, but it depends on your internet speed as its going to send your app bundle to Galaxy servers.
> Galaxy builds a new Docker image that contains your app bundle and then deploy containers using it, [read more](https://galaxy-guide.meteor.com/container-environment.html).
You can check your logs on Galaxy, including the part that Galaxy is building your Docker image and deploying it.
### 8.5: Access the app and enjoy
Now you should be able to access your Galaxy dashboard at `https://galaxy.meteor.com/app/react-meteor-3.meteorapp.com`.
You can also access your app on Galaxy 2.0 which is currently in beta at `https://galaxy-beta.meteor.com/<your-username>/us-east-1/apps/<your-app-name>.meteorapp.com`. Remember to use your own subdomain instead of `react-meteor-3`.
You can access the app at [react-meteor-3.meteorapp.com](https://react-meteor-3.meteorapp.com/)! Just use your subdomain to access yours!
> We deployed to Galaxy running in the US (us-east-1), we also have Galaxy running in other regions in the world, check the list [here](https://galaxy-guide.meteor.com/deploy-region.html).
This is huge, you have your app running on Galaxy, ready to be used by anyone in the world!

View File

@@ -0,0 +1,17 @@
## 9: Next Steps
You have completed the tutorial!
By now, you should have a good understanding of working with Meteor and Vue.
::: info
You can find the final version of this app in our [GitHub repository](https://github.com/meteor/meteor3-react).
:::
Here are some options for what you can do next:
- Check out the complete [documentation](https://v3-docs.meteor.com/) to learn more about Meteor 3.
- Read the [Galaxy Guide](https://galaxy-guide.meteor.com/) to learn more about deploying your app.
- Join our community on the [Meteor Forums](https://forums.meteor.com/) and the [Meteor Lounge on Discord](https://discord.gg/hZkTCaVjmT) to ask questions and share your experiences.
We can't wait to see what you build next!

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

View File

@@ -0,0 +1,23 @@
# Meteor.js 3 + React
In this tutorial, we will create a simple To-Do app using [React](https://react.dev/) and Meteor 3.0. Meteor works well with other frameworks like [Blaze](https://www.blazejs.org/), [Vue 3](https://vuejs.org/), [Solid](https://www.solidjs.com/), and [Svelte](https://svelte.dev/).
React is a popular JavaScript library for building user interfaces. It allows you to create dynamic and interactive applications by composing UI components. React uses a declarative approach, where you define how the UI should look based on the state, and it efficiently updates the view when the state changes. With JSX, a syntax extension that combines JavaScript and HTML, React makes it easy to create reusable components that manage their own state and render seamlessly in the browser.
To start building your React app, you'll need a code editor. If you're unsure which one to choose, [Visual Studio Code](https://code.visualstudio.com/) is a good option. After installing it, you can enhance your experience by adding extensions like [Meteor Toolbox](https://marketplace.visualstudio.com/items?itemName=meteor-toolbox.meteor-toolbox).
Lets begin building your app!
# Table of Contents
[[toc]]
<!-- @include: ./1.creating-the-app.md-->
<!-- @include: ./2.collections.md-->
<!-- @include: ./3.forms-and-events.md-->
<!-- @include: ./4.update-and-remove.md-->
<!-- @include: ./5.styles.md-->
<!-- @include: ./6.filter-tasks.md-->
<!-- @include: ./7.adding-user-accounts.md-->
<!-- @include: ./8.deploying.md-->
<!-- @include: ./9.next-steps.md-->