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 🙏

© 2024 – Pkg Stats / Ryan Hefner

tracker-component

v1.3.21

Published

Easy reactive React Components with Meteor and Tracker

Downloads

92

Readme

Tracker.Component

Current version 1.3.21

Features

  1. Easy to use, manages Tracker for you using autorun, and your subscriptions using the subscribe method, you don't have to manually setup the reactivity bindings or start/stop subscriptions, we promise!
  2. Subscriptions are managed through the built in this.subscribe and ensures your subscription are correctly stopped when your component unmounts.
  3. Composition and Class Inheritance is easy to achieve, and the preferred methods rather than using Mixins, read more about Mixin.
  4. Lightweight implementation, have a look in index.jsx, there's no magic going on, only 48 lines of code.
  5. Server Side Rendering supported (with data managed trough FlowRouter SSR).

Tracker.Component is an improvement to what other methods offer (see comparison) for React. Using Tracker.Component you are no longer required to "freeze" all your reactivity in a single method or composition. You set the state from the reactive data sources (e.g: collection.find().fetch() or Session.get('foo') in this.autorun, which is also reactive to changes in this.props or this.state. Have fun!

Installation

npm i --save tracker-component

Using Tracker.Component

Go to using with Meteor 1.2

meteor create myapp --release [email protected]

In this example we render a couple cars from MongoDB.

You'll probably recognize the autorun and subscribe from Blaze's Tracker implementation. That's the core idea, simplicity.

npm i --save react react-dom

// main.jsx

import React from 'react';
import Tracker from 'tracker-component';

Models = new Mongo.Collection('models');
if (Meteor.isServer) {
  Meteor.publish('cars', () => Models.find());
}

class Models extends Tracker.Component {
  constructor(props) {
    super(props);
    this.subscribe('cars');
    this.autorun(() => {
      this.setState({
        cars: Models.find().fetch()
      });
    });
  }
}

const Cars = ({ cars = [] }) => (
  <ul className="cars">
    {cars.map(car =>
      <li className="car">{car.brand} {car.model}</li>
    )}
  </ul>
);

if (Meteor.isClient) {
  Meteor.startup(() => {
    ReactDOM.render(<Models><Cars /></Models>, document.body);
  });
}

Fill with data from the server.

Try adding new car models while running meteor, you'll notice it is fully reactive throughout the whole stack.


// Bootstrap database with some cars.
Meteor.startup(function() {
  let models = {
    "Volvo": ['XC90', 'V90', 'V70'],
    "Tesla": ['Model S', 'Model X', 'Model 3', 'Roadster'],
    "DeLorean": ["DMC-12"]
  };

  Object.keys(models).forEach(brand => {
    models[brand].forEach(model => {
      car = { brand: brand, model: model };
      Models.upsert(car, car);
    });
  });
});

Meteor.publish('brand', (brand) => {
  // Simulate network latency to show the loader.
  // Meteor._sleepForMs(2000);
  if (brand) {
    return Models.find({ brand: brand });
  }
  return Models.find();
});

Result on the client:


<body>
  <ul class="cars">
    <li>Volvo XC90</li>
    <li>Volvo V90</li>
    <li>Volvo V70</li>
    <li>Tesla Model S</li>
    <li>Tesla Model X</li>
    <li>Tesla Model 3</li>
    <li>Tesla Roadster</li>
    <li>DeLorean DMC-12</li>
  </ul>
<body>

Full example: What about adding a loading gif?

http://github.com/studiointeract/tracker-component-example

We got you're back on this one too! And have a look below, we've also added a select button to switch between selected car brand.

Just add this.subscriptionsReady() to your autorun like below and you will get a reactive boolean to use for a ready flag.

Notice! We advice in using "ready" flag rather "loading" due to that the data will default be ready when rendered on the server. The reason is basically to avoid having React complaining about different markup on server and client, which would happen when using the loading pattern.

// main.jsx

Models = new Mongo.Collection('models');

Brands = class Cars extends Tracker.Component {
  constructor(props) {
    super(props);
    this.state = {
      brand: this.props.brand
    };
    this.autorun(() => {
      this.subscribe( 'brand', this.state.brand );
    });
    this.autorun(() => {
      this.setState({
        ready: this.subscriptionsReady(),
        cars: Models.find({ brand: this.state.brand }).fetch()
      });
    });
  }

  handleChange() {
    this.setState({brand: this.refs.brand.value});
  }

  render() {
    let {cars = []} = this.state;
    let selectBrand = this.handleChange.bind(this);
    let brands = ["Volvo", "Tesla", "DeLorean"];

    return (
      <div>
        <select ref="brand" onChange={selectBrand} defaultValue={this.state.selected}>
          {brands.map((brand, i) =>
            <option value={brand} key={i}>{brand}</option>
          )}
          {super.render()}
        </select>
      </div>
    );
  }
}
Brands.propTypes = {
  brand: React.PropTypes.string
};
Brands.defaultProps = { brand: 'Volvo' };

const Cars = ({ cars = [], ready }) => (
  <ul className={["cars", ready ? "ready" : ""].join(' ')}>
    {cars.map((car, i) =>
      <li className="car" key={i}>{car.brand} {car.model}</li>
    )}
  </ul>
);

if (Meteor.isClient) {
  ReactDOM.render(<Brands><Cars /></Brands>, document.body);
}

Here's an example on some CSS to show a loading icon when we're waiting for the cars to arrive to the client. We have also added a transition with a delay that we reset when the class ready is set, this is to avoid flashing the icon when the data is really fast, which is usually the case.

Add Meteor._sleepForMs(2000); in the publication to get view of the beautiful loading icon.


.cars:before {
  content: '';
  display: block;
  position: absolute;
  top: 16px;
  left: 0;
  width: 100%;
  height: 20px;
  background: url("/loader.gif") no-repeat center center;
  background-size: auto 20px;
  transition: opacity 0.1s ease 1s;
  opacity: 1;
}
.cars.ready:before {
  opacity: 0;
  transition: none;
}

Add Server Side Rendering

First off, remove the rendering to DOM from main.jsx:

if (Meteor.isClient) {
  ReactDOM.render(<Brands><Cars /></Brands>, document.body);
}

Add some packages, both Meteor and NPM.

meteor add kadira:flow-router-ssr
npm i --save react-mounter (for React 0.14.7)

If you prefer React 15.x you can use react-mount-layout for that, a fork of react-mounter: npm i --save react-mount-layout@^15.x (for React 15.x, will replace with react-mounter when supporting React 15.x)

// router.jsx

import React from 'react';
import { FlowRouter } from 'meteor/kadira:flow-router-ssr';

const MainLayout = ({content}) => (
  <main>{content}</main>
);

FlowRouter.route("/", {
  action() {
    ReactLayout.render(MainLayout, {
      content: <Cars />
    });
  }
});

Result on the server:


<main>
  <ul class="cars">
    <li>Volvo XC90</li>
    <li>Volvo V90</li>
    <li>Volvo V70</li>
    <li>Tesla Model S</li>
    <li>Tesla Model X</li>
    <li>Tesla Model 3</li>
    <li>Tesla Roadster</li>
    <li>DeLorean DMC-12</li>
  </ul>
</main>

Using Tracker.Component (Meteor 1.2)

Installation

meteor add studiointeract:[email protected]

In this example we render a couple cars from MongoDB.

You'll probably recognize the autorun and subscribe from Blaze's Tracker implementation. That's the core idea, simplicity.

Models = new Mongo.Collection('models');

Cars = class Cars extends Tracker.Component {
  constructor(props) {
    super(props);
    this.autorun(() => {
      this.setState({
        cars: Models.find().fetch()
      });
    })
  }

  render() {
    let {cars = []} = this.state;
    return (
      <ul className="cars">
        {cars.map(car =>
          <li className="car">{car.brand} {car.model}</li>
        )}
      </ul>
    );
  }
}

if (Meteor.isClient) {
  Meteor.startup(() => {
    ReactDOM.render(<Cars />, document.body);
  });
}

Fill with data from the server.

Try adding new car models while running meteor, you'll notice it is fully reactive throughout the whole stack.

// Bootstrap database with some cars.
Meteor.startup(function() {
  let models = {
    "Volvo": ['XC90', 'V90', 'V70'],
    "Tesla": ['Model S', 'Model X', 'Model 3', 'Roadster'],
    "DeLorean": ["DMC-12"]
  };

  Object.keys(models).forEach(brand => {
    models[brand].forEach(model => {
      car = { brand: brand, model: model };
      Models.upsert(car, car);
    });
  });
});

// Publish cars by brand or all of them.
Meteor.publish('brand', (brand) => {
  // Simulate network latency to show the loader.
  // Meteor._sleepForMs(2000);
  if (brand) {
    return Models.find({ brand: brand });
  }
  return Models.find();
});

Result on the client:


<body>
  <ul class="cars">
    <li>Volvo XC90</li>
    <li>Volvo V90</li>
    <li>Volvo V70</li>
    <li>Tesla Model S</li>
    <li>Tesla Model X</li>
    <li>Tesla Model 3</li>
    <li>Tesla Roadster</li>
    <li>DeLorean DMC-12</li>
  </ul>
</body>

Add Server Side Rendering

meteor add kadira:flow-router-ssr
meteor add kadira:react-layout

// router.jsx

const MainLayout = ({content}) => (
  <main>{content}</main>
);

FlowRouter.route("/", {
  action() {
    ReactLayout.render(MainLayout, {
      content: <Cars />
    });
  }
});

Result on the server:


<main>
  <ul class="cars">
    <li>Volvo XC90</li>
    <li>Volvo V90</li>
    <li>Volvo V70</li>
    <li>Tesla Model S</li>
    <li>Tesla Model X</li>
    <li>Tesla Model 3</li>
    <li>Tesla Roadster</li>
    <li>DeLorean DMC-12</li>
  </ul>
</main>

Full example: What about adding a loading gif?

http://github.com/studiointeract/tracker-component-example

We got you're back on this one too! And have a look below, we've also added a select button to switch between selected car brand.

Just add this.subscriptionsReady() to your autorun like below and you will get a reactive boolean to use for a ready flag.

Notice! We advice in using "ready" flag rather "loading" due to that the data will default be ready when rendered on the server. The reason is basically to avoid having React complaining about different markup on server and client, which would happen when using the loading pattern.


Models = new Mongo.Collection('models');

Cars = class Cars extends Tracker.Component {
  constructor(props) {
    super(props);
    this.state = {
      brand: this.props.brand
    };
    this.autorun(() => {
      this.subscribe( 'models', this.state.brand );
    });
    this.autorun(() => {
      this.setState({
        ready: this.subscriptionsReady(),
        cars: Models.find({ brand: this.state.brand }).fetch()
      });
    });
  }

  handleChange() {
    this.setState({brand: this.refs.brand.value});
  }

  render() {
    let {cars = []} = this.state;
    let selectBrand = this.handleChange.bind(this);
    let brands = ["Volvo", "Tesla", "DeLorean"];

    return (
      <div>
        <select ref="brand" onChange={selectBrand} defaultValue={this.state.selected}>
          {brands.map((brand, i) =>
            <option value={brand} key={i}>{brand}</option>
          )}
        </select>
        <ul className={["cars",
          this.state.ready ? "ready" : ""].join(' ')}>
          {cars.map((car, i) =>
            <li className="car" key={i}>{car.brand} {car.model}</li>
          )}
        </ul>
      </div>
    );
  }
}
Cars.propTypes = {
  brand: React.PropTypes.string
};
Cars.defaultProps = { brand: 'Volvo' };

Increase Performance using PureRenderMixin

As described on its own documentation this is a utility to increase rendering performance for react components where

  • the render function is a pure function of its props
  • the props are not nested data structures (more on this on the docs)

Here's the official ES6 example:

import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

Comparison

| | Tracker.Component | TrackerReact | ReactMeteorData | react-komposer | |:--------------- |:------------------:|:-----------------:|:----------------:|:--------------------:| | Lines of code | 48 | 148 | 200 | 292 | | ES6 Class Inheritance | Yes | - | - | - | | Composition | Yes | Yes | createContainer | Yes | | Mixin | - | - | Yes | - | | Subscriptions | this.subscribe | - | - | - | | SSR | Yes | Partial | Partial | Partial | | Reactivity | this.autorun | render | getMeteorData | composeWithTracker | | NPM module | Yes | - | - | Yes |

Server Side Rendering

To get the server to render your component with prefilled data, you will need to have that data with known methods (ReactMeteorData, createContainer and TrackerReact) to manually load specific for the server, this method can potentially render more data then the client expected from a subscription and React will definitely complain when the client version takes over.

The issue is that you have to match up the selectors for find() with the current subscription. With Tracker.Component which has subscription support built in, you setup these in the constructor together with your find() for the collection, this ensures the data available is equally specified on both server and client.

Subscriptions

With subscription management built in, your component will unsubscribe to the data you needed for the component when it is unmounted/destroyed, compared to known methods (ReactMeteorData, createContainer, TrackerReact and react-komposer) you will need to manage this yourself and potentially overload the client with data from multiple subscriptions that was never stopped, when the user is moving around your application.

With Tracker.Component you subscribe to publications per component like this:

Notice! The autorun method is also reactive to changes on this.props and this.state, which makes it possible to react accordingly on changes to these and change the subscriptions.

Cars = class Cars extends Tracker.Component {
  constructor(props) {
    super(props);
    this.state = {
      brand: this.props.brand
    };
    this.subscribe('brands');
    this.autorun(() => {
      this.subscribe( 'brand', this.state.brand );
    });
  }
}

Class Inheritance

With Class Inheritance we talk about the method we can extend existing Components with new functionality and a method of overloading existing method with your own, the benefits is that you are in full control of the component how it behaves and if you don't like how a particular method or handler does things, you can replace it with your own implementation.

This is the default method for Tracker.Component because it requires you to write the least amount of code.

Composition

With Composition in React we mean the method to split up data management and pure rendering components, composition is actually a known method in mathematics, "the pointwise application of one function to the result of another to produce a third function" (ref. Function Composition).

Composition can be achieved with known methods (createContainer, TrackerReact and react-komposer) by passing your data management function to the compostion method which resolves in a method that takes your Component as an argument.

With Tracker.Component this can be achivieved with (full example):

// main.jsx

import React from 'react';
import Tracker from 'tracker-component';

Models = new Mongo.Collection('models');
if (Meteor.isServer) {
  Meteor.publish('cars', brand => Models.find({ brand: brand }));
}

class Composition extends Tracker.Component {
  constructor(props) {
    super(props);
    this.subscribe('cars', this.props.brand);
    this.autorun(() => {
      this.setState({
        cars: Models.find({ brand: this.props.brand }).fetch()
      });
    });
  }
}

const Cars = ({ cars = [] }) => (
  <ul className="cars">
    {cars.map(car =>
      <li className="car">{car.brand} {car.model}</li>
    )}
  </ul>
);

if (Meteor.isClient) {
  Meteor.startup(() => {
    ReactDOM.render(<Composition brand={ 'Volvo' }><Cars /></Composition>, document.body);
  });
}

Results:


<body>
  <ul class="cars">
    <li>Volvo XC90</li>
    <li>Volvo V90</li>
    <li>Volvo V70</li>
  </ul>
</body>

Mixin

Mixins are a method of previous versions of React, we used them to extend the components with extra features on top, the new way to achive the same functionality is through Composition or Class Inheritance. Don't forget to read the article on Mixins by Dan Abramov, Mixins Are Dead. Long Live Composition.

Stopping computations

Usually you don't need to stop the computation as it will automatically will be stopped when the component is destroyed, though notice that if you re-implement componentWillUnmount, be sure to call the super implementation as noticed below.

Supported from version 1.3.18.

Alt 1: Stopping by reference.

Use the returned reference to the computation and stop it from there.

const comp = this.autorun(() => {});
comp.stop();

Alt 2: Stopping from inside the autorun.

You can always just pick up the reference to the computation passed in the arguments of your autorun implementation and stop it.

const comp = this.autorun(c => {
  c.stop();
});

Notes

Beware, if you re-implement componentWillUpdate or componentWillUnmount, don't forget to call the super implementation (super.componentWillUpdate() or super.componentWillUnmount()).

Credits

Made by the creative folks at Studio Interact.