npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

parse-mobx

v4.3.0

Published

A wrapper for ParseJS SDK to make Parse Objects observable in Mobx

Readme

Getting Started

📚 API Documentation

https://imana97.github.io/parse-mobx/

📦 Installation

Parse-MobX requires MobX and Parse SDK as peer dependencies:

npm install parse-mobx mobx parse

# For TypeScript projects, also install types
npm install -D @types/parse

Requirements

  • MobX: ^6.13.0
  • Parse SDK: ^6.1.1
  • Node.js: ^18.0.0 or higher
  • TypeScript: ^5.0.0 (if using TypeScript)

🚀 Quick Start

1. Parse Server Setup & Configuration

First, initialize Parse and configure ParseMobx:

import Parse from 'parse';
import { configureParseMobx } from 'parse-mobx';

// Initialize Parse
Parse.initialize(
  'YOUR_APP_ID',
  'YOUR_JAVASCRIPT_KEY'
);
Parse.serverURL = 'https://your-parse-server.com/parse';

// Configure ParseMobx with your Parse instance
configureParseMobx(Parse);

2. Basic Usage

import { ParseMobx } from 'parse-mobx';
import { observable, action } from 'mobx';

// Create a Parse object and make it observable
const parseObject = await new Parse.Query('Todo').first();
const observableTodo = new ParseMobx(parseObject);

// Now you can use it reactively in your components
console.log(observableTodo.get('title')); // Gets the title
observableTodo.set('completed', true).save(); // Updates and saves

📝 Complete Todo App Example

Here's a comprehensive React Todo application using parse-mobx, MobX, and React:

Store Setup (stores/TodoStore.ts)

import { makeObservable, observable, action, runInAction } from 'mobx';
import Parse from 'parse';
import { ParseMobx, MobxStore, configureParseMobx } from 'parse-mobx';

// Initialize Parse and configure ParseMobx
Parse.initialize('YOUR_APP_ID', 'YOUR_JS_KEY');
Parse.serverURL = 'https://your-parse-server.com/parse';
configureParseMobx(Parse);

export class TodoStore extends MobxStore {
  @observable newTodoText = '';
  @observable filter: 'all' | 'active' | 'completed' = 'all';

  constructor() {
    super('Todo'); // Pass Parse class name
    makeObservable(this);
  }

  @action
  setNewTodoText(text: string) {
    this.newTodoText = text;
  }

  @action
  setFilter(filter: 'all' | 'active' | 'completed') {
    this.filter = filter;
  }

  @action
  async addTodo() {
    if (!this.newTodoText.trim()) return;
    
    await this.createObject({
      title: this.newTodoText.trim(),
      completed: false,
      createdAt: new Date()
    }, { updateList: true });
    
    this.newTodoText = '';
  }

  @action
  async toggleTodo(todo: ParseMobx) {
    const completed = !todo.get('completed');
    await todo.set('completed', completed).save();
  }

  @action
  async deleteTodo(todo: ParseMobx) {
    await this.deleteObject(todo);
  }

  @action
  async loadTodos() {
    const query = new Parse.Query('Todo');
    query.ascending('createdAt');
    this.fetchObjects(query);
  }

  get filteredTodos() {
    switch (this.filter) {
      case 'active':
        return this.objects.filter(todo => !todo.get('completed'));
      case 'completed':
        return this.objects.filter(todo => todo.get('completed'));
      default:
        return this.objects;
    }
  }

  get activeTodosCount() {
    return this.objects.filter(todo => !todo.get('completed')).length;
  }
}

// Create a singleton instance
export const todoStore = new TodoStore();

React Components

Main Todo App (components/TodoApp.tsx)

import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { todoStore } from '../stores/TodoStore';
import { TodoInput } from './TodoInput';
import { TodoList } from './TodoList';
import { TodoFilters } from './TodoFilters';
import './TodoApp.css';

export const TodoApp = observer(() => {
  useEffect(() => {
    todoStore.loadTodos();
  }, []);

  if (todoStore.loading) {
    return (
      <div className="todo-app">
        <div className="loading">Loading todos...</div>
      </div>
    );
  }

  return (
    <div className="todo-app">
      <header className="header">
        <h1>todos</h1>
        <TodoInput />
      </header>
      
      <main className="main">
        <TodoList />
      </main>
      
      <footer className="footer">
        <TodoFilters />
        <span className="todo-count">
          {todoStore.activeTodosCount} items left
        </span>
      </footer>
      
      {todoStore.parseError && (
        <div className="error">
          Error: {todoStore.parseError.message}
          <button onClick={() => todoStore.clearError()}>
            Dismiss
          </button>
        </div>
      )}
    </div>
  );
});

Todo Input (components/TodoInput.tsx)

import React from 'react';
import { observer } from 'mobx-react-lite';
import { todoStore } from '../stores/TodoStore';

export const TodoInput = observer(() => {
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    todoStore.addTodo();
  };

  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      todoStore.addTodo();
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        className="new-todo"
        placeholder="What needs to be done?"
        value={todoStore.newTodoText}
        onChange={(e) => todoStore.setNewTodoText(e.target.value)}
        onKeyPress={handleKeyPress}
        autoFocus
      />
    </form>
  );
});

Todo List (components/TodoList.tsx)

import React from 'react';
import { observer } from 'mobx-react-lite';
import { todoStore } from '../stores/TodoStore';
import { TodoItem } from './TodoItem';

export const TodoList = observer(() => {
  if (todoStore.filteredTodos.length === 0) {
    return (
      <div className="no-todos">
        {todoStore.filter === 'completed' 
          ? 'No completed todos' 
          : 'No todos yet. Add one above!'}
      </div>
    );
  }

  return (
    <ul className="todo-list">
      {todoStore.filteredTodos.map((todo) => (
        <TodoItem key={todo.getId()} todo={todo} />
      ))}
    </ul>
  );
});

Individual Todo Item (components/TodoItem.tsx)

import React, { useState } from 'react';
import { observer } from 'mobx-react-lite';
import { ParseMobx } from 'parse-mobx';
import { todoStore } from '../stores/TodoStore';

interface TodoItemProps {
  todo: ParseMobx;
}

export const TodoItem = observer(({ todo }: TodoItemProps) => {
  const [isEditing, setIsEditing] = useState(false);
  const [editText, setEditText] = useState(todo.get('title'));

  const handleSave = async () => {
    if (editText.trim()) {
      await todo.set('title', editText.trim()).save();
      setIsEditing(false);
    }
  };

  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      handleSave();
    } else if (e.key === 'Escape') {
      setEditText(todo.get('title'));
      setIsEditing(false);
    }
  };

  const isCompleted = todo.get('completed');
  const title = todo.get('title');

  return (
    <li className={`todo-item ${isCompleted ? 'completed' : ''}`}>
      <div className="view">
        <input
          className="toggle"
          type="checkbox"
          checked={isCompleted}
          onChange={() => todoStore.toggleTodo(todo)}
        />
        
        {isEditing ? (
          <input
            className="edit"
            value={editText}
            onChange={(e) => setEditText(e.target.value)}
            onBlur={handleSave}
            onKeyPress={handleKeyPress}
            autoFocus
          />
        ) : (
          <label onDoubleClick={() => setIsEditing(true)}>
            {title}
          </label>
        )}
        
        <button
          className="destroy"
          onClick={() => todoStore.deleteTodo(todo)}
        >
          ×
        </button>
      </div>
      
      {todo.loading && <div className="todo-loading">Saving...</div>}
    </li>
  );
});

Filter Controls (components/TodoFilters.tsx)

import React from 'react';
import { observer } from 'mobx-react-lite';
import { todoStore } from '../stores/TodoStore';

export const TodoFilters = observer(() => {
  const filters = [
    { key: 'all' as const, label: 'All' },
    { key: 'active' as const, label: 'Active' },
    { key: 'completed' as const, label: 'Completed' },
  ];

  return (
    <div className="filters">
      {filters.map(({ key, label }) => (
        <button
          key={key}
          className={todoStore.filter === key ? 'selected' : ''}
          onClick={() => todoStore.setFilter(key)}
        >
          {label}
        </button>
      ))}
    </div>
  );
});

🔄 Real-time Updates with LiveQuery

Enable real-time synchronization across clients:

export class TodoStore extends MobxStore {
  constructor() {
    super('Todo');
    makeObservable(this);
    
    // Subscribe to real-time updates
    this.subscribe();
    
    // Setup event handlers
    this.onCreate((todo) => {
      console.log('New todo created:', todo.get('title'));
    });
    
    this.onUpdate((todo) => {
      console.log('Todo updated:', todo.get('title'));
    });
    
    this.onDelete((todo) => {
      console.log('Todo deleted');
    });
  }
}

🎨 CSS Styling (TodoApp.css)

.todo-app {
  max-width: 550px;
  margin: 0 auto;
  padding: 20px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.header h1 {
  font-size: 3rem;
  color: #b83f45;
  text-align: center;
  margin-bottom: 20px;
}

.new-todo {
  width: 100%;
  padding: 15px;
  font-size: 1.2rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
}

.todo-list {
  list-style: none;
  padding: 0;
  margin: 20px 0;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
  position: relative;
}

.todo-item.completed label {
  text-decoration: line-through;
  color: #999;
}

.toggle {
  margin-right: 15px;
}

.destroy {
  position: absolute;
  right: 10px;
  background: none;
  border: none;
  font-size: 1.5rem;
  color: #cc9a9a;
  cursor: pointer;
}

.filters {
  display: flex;
  gap: 10px;
  margin-bottom: 10px;
}

.filters button {
  padding: 5px 10px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
}

.filters button.selected {
  background: #b83f45;
  color: white;
}

.loading, .no-todos {
  text-align: center;
  padding: 20px;
  color: #999;
}

.error {
  background: #f8d7da;
  color: #721c24;
  padding: 10px;
  border-radius: 4px;
  margin-top: 10px;
}

⚙️ Configuration System

ParseMobx requires explicit configuration to avoid global namespace pollution and give you full control over your Parse setup:

Benefits of Configuration

  • No global pollution - Parse is not set on the global object
  • Full control - Configure Parse exactly how you need it
  • Better testing - Easy to mock and test with different Parse instances
  • Clean architecture - Clear separation between Parse setup and usage

Configuration API

import { configureParseMobx } from 'parse-mobx';
import Parse from 'parse';

// Configure with your Parse instance
configureParseMobx(Parse);

// Now you can use ParseMobx and MobxStore
import { ParseMobx, MobxStore } from 'parse-mobx';

Error Handling

If you try to use ParseMobx without configuration, you'll get a clear error:

// This will throw an error:
const todo = new ParseMobx(parseObject);
// Error: ParseMobx is not configured. Please call configureParseMobx(parse) first.

🛠 Advanced Features

Custom Parse Object Classes

// Parse is already configured above, so we can use it directly
// Define a custom Parse object
class Todo extends Parse.Object {
  constructor() {
    super('Todo');
  }

  static spawn(attrs: any) {
    const todo = new Todo();
    return todo.set(attrs);
  }
}

// Register the subclass
Parse.Object.registerSubclass('Todo', Todo);

// Use with ParseMobx
const todo = Todo.spawn({ title: 'Learn Parse-MobX' });
await todo.save();
const observableTodo = new ParseMobx(todo);

Optimistic Updates

@action
async toggleTodoOptimistic(todo: ParseMobx) {
  // Update UI immediately
  const oldValue = todo.get('completed');
  todo.set('completed', !oldValue);
  
  try {
    // Save to server
    await todo.save();
  } catch (error) {
    // Revert on error
    todo.set('completed', oldValue);
    console.error('Failed to update todo:', error);
  }
}

This example demonstrates the full power of parse-mobx with reactive UI updates, real-time synchronization, and clean separation of concerns using MobX stores!

🧪 Testing

The configuration system makes testing much easier:

import { configureParseMobx, resetConfiguration } from 'parse-mobx';

// Mock Parse for testing
const mockParse = {
  Object: jest.fn(),
  Query: jest.fn(),
  // ... other Parse methods
};

// Configure with mock
configureParseMobx(mockParse);

// Run your tests
const store = new MobxStore('Test');
store.fetchObjects();

// Reset between tests
resetConfiguration();