Skip to main content
React is a JavaScript library for building reactive user interfaces. Meteor provides seamless integration with React through the react-meteor-data package, allowing you to build modern, reactive applications.

Installation

Install React in your Meteor application:
meteor npm install --save react react-dom
For reactive data integration, add the react-meteor-data package:
meteor add react-meteor-data

Quick Start

Create a new React app with Meteor:
meteor create my-app --react
This creates a basic structure with React configured and ready to use.

Basic Setup

Client Entry Point

Render your React application in /client/main.jsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Meteor } from 'meteor/meteor';
import { App } from '/imports/ui/App';

Meteor.startup(() => {
  const container = document.getElementById('react-target');
  const root = createRoot(container);
  root.render(<App />);
});

Root Component

Create your root component in /imports/ui/App.jsx:
import React from 'react';
import { Hello } from './Hello.jsx';
import { Info } from './Info.jsx';

export const App = () => (
  <div>
    <h1>Welcome to Meteor!</h1>
    <Hello />
    <Info />
  </div>
);

Reactive Data with useTracker

The useTracker hook integrates React components with Meteor’s reactive data system.

Basic Usage

import { useTracker } from 'meteor/react-meteor-data';
import { Meteor } from 'meteor/meteor';

function UserProfile() {
  const currentUser = useTracker(() => Meteor.user(), []);
  
  return <h1>Hello {currentUser?.username}</h1>;
}

With Subscriptions

import { useTracker } from 'meteor/react-meteor-data';
import { TasksCollection } from '../api/tasks';

function TaskList({ listId }) {
  const { tasks, isLoading } = useTracker(() => {
    const handle = Meteor.subscribe('tasks', listId);
    
    return {
      isLoading: !handle.ready(),
      tasks: TasksCollection.find({ listId }).fetch()
    };
  }, [listId]);
  
  if (isLoading) {
    return <div>Loading...</div>;
  }
  
  return (
    <ul>
      {tasks.map(task => (
        <li key={task._id}>{task.title}</li>
      ))}
    </ul>
  );
}

Modern Hooks API

useSubscribe

Convenient subscription management:
import { useFind, useSubscribe } from 'meteor/react-meteor-data';
import { LinksCollection } from '../api/links';

export const Info = () => {
  const isLoading = useSubscribe('links');
  const links = useFind(() => LinksCollection.find());

  if (isLoading()) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>
        {links.map(link => (
          <li key={link._id}>
            <a href={link.url} target="_blank">{link.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

useFind

Optimized list rendering with controlled document references:
import { useFind, useSubscribe } from 'meteor/react-meteor-data';
import { memo } from 'react';
import { PostsCollection } from '../api/posts';

const PostItem = memo(({ post }) => (
  <li>{post.title}</li>
));

function PostsList({ groupId }) {
  useSubscribe('posts', groupId);
  const posts = useFind(() => PostsCollection.find({ groupId }), [groupId]);
  
  return (
    <ul>
      {posts.map(post => (
        <PostItem key={post._id} post={post} />
      ))}
    </ul>
  );
}
useFind requires a cursor, not .fetch(). It uses Cursor.observe to efficiently update only changed documents.

withTracker HOC

For class components or legacy code:
import { withTracker } from 'meteor/react-meteor-data';
import { TasksCollection } from '../api/tasks';

function TaskList({ currentUser, isLoading, tasks }) {
  return (
    <div>
      <h1>Hello {currentUser?.username}</h1>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <ul>
          {tasks.map(task => (
            <li key={task._id}>{task.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default withTracker(({ listId }) => {
  const handle = Meteor.subscribe('tasks', listId);
  
  return {
    currentUser: Meteor.user(),
    isLoading: !handle.ready(),
    tasks: TasksCollection.find({ listId }).fetch()
  };
})(TaskList);

Suspense Support (Meteor 3.0+)

Use suspendable hooks with React Suspense:
import { useTracker, useSubscribe } from 'meteor/react-meteor-data/suspense';
import { TasksCollection } from '../api/tasks';

function Tasks() {
  // Component will suspend until subscription is ready
  useSubscribe('tasks');
  
  const { username } = useTracker('user', () => Meteor.user());
  const tasks = useTracker(
    'tasksByUser',
    () => TasksCollection.find(
      { username },
      { sort: { createdAt: -1 } }
    ).fetchAsync()
  );
  
  return (
    <ul>
      {tasks.map(task => (
        <li key={task._id}>{task.title}</li>
      ))}
    </ul>
  );
}
Suspendable hooks require a unique key as the first argument to identify computations.

Routing with React Router

Meteor works seamlessly with React Router:
meteor npm install react-router-dom
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { TaskList } from './pages/TaskList';

export const App = () => (
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/tasks/:listId" element={<TaskList />} />
    </Routes>
  </BrowserRouter>
);

Integration Patterns

Use React components inside Blaze templates with react-template-helper:
meteor add react-template-helper
In your Blaze template:
<template name="userDisplay">
  <div>Hello, {{username}}</div>
  <div>{{> React component=UserAvatar userId=_id}}</div>
</template>
Define the component helper:
import { Template } from 'meteor/templating';
import UserAvatar from './UserAvatar.js';

Template.userDisplay.helpers({
  UserAvatar() {
    return UserAvatar;
  }
});

Best Practices

The useTracker hook is preferred over withTracker for function components. Hooks provide better composition and are easier to test.
Provide dependency arrays to useTracker to retain computations and prevent unnecessary re-runs:
const user = useTracker(() => Meteor.user(), []);
const tasks = useTracker(() => Tasks.find({ listId }).fetch(), [listId]);
For rendering large lists from collections, useFind provides significant performance benefits by controlling object references.
Wrap list items with React.memo() to prevent unnecessary re-renders:
const ListItem = memo(({ task }) => (
  <li>{task.title}</li>
));

Resources

React Tutorial

Step-by-step tutorial for building React apps with Meteor

React Packages

Source code and documentation for react-meteor-data

React Documentation

Official React documentation and guides

React Router

Client-side routing for React applications

Build docs developers (and LLMs) love