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

mst-reference-pool

v0.2.2

Published

MobX-State-Tree extension to create reference pools

Downloads

1,129

Readme

MST Reference Pool

mst-reference-pool is a MobX-State-Tree extension that allows you to use references to a pool of model instances in any store.

Think of it like a hidden types.array that you can point references to, plus a garbage collector to get rid of any instances that nothing is referencing anymore.

When would you use mst-reference-pool?

Whenever you have a frequently-changing array of instances and also have other references to those instances, mst-reference-pool becomes a good option.

Let's look at an example ... say, an Instagram-like app. There's a feed of posts and also an optional currentPost.

import { types } from "mobx-state-tree"
import { PostModel } from "./post"

const RootStore = types
  .model("RootStore", {
    feed: types.array(PostModel),
    currentPost: types.maybe(types.reference(PostModel)),
  })
  .actions((store) => ({
    setFeed(newPosts) {
      store.feed.replace(newPosts)
    },
    setCurrentPost(newPost) {
      store.currentPost = newPost
    },
  }))

The thing with the feed is that you could scroll or refresh, and then the currentPost would refer to a post that is no longer in the feed. This causes a reference error.

You might think you could make the currentPost into its own types.maybe(PostModel), but when that post is in view for both the feed and the currentPost, now you have duplicate data (and identifiers).

This is where mst-reference-pool shines!

Implementing mst-reference-pool

Taking our Instagram-like app above, let's convert it to use a reference pool.

import { types } from "mobx-state-tree"
+import { withReferencePool } from "mst-reference-pool"
import { PostModel } from "./post"

const RootStore = types
  .model("RootStore", {
+   pool: types.array(PostModel),
    feed: types.array(types.reference(PostModel)),
    currentPost: types.maybe(types.reference(PostModel)),
  })
+ .extend(withReferencePool(PostModel))
  .actions((store) => ({
    setFeed(newPosts) {
+     const posts = store.addAllToPool(newPosts)
      store.feed.replace(posts)
    },
    setCurrentPost(newPost) {
+     const post = store.addToPool(newPost)
      store.currentPost = post
    },
  }))

As you can see here, the primary difference is that we now have a pool prop that contains the posts, and everything else is just a reference to those posts.

Before we set the feed, we add the new posts to the pool with store.addAllToPool, and then use those to establish the references.

We can do the same for a single reference. We just do store.addToPool and then set the reference.

These references will always point to one instance in the pool. If addToPool or addAllToPool find an existing instance with the same identifier, they'll run applySnapshot on that instance instead. This prevents duplicate items in your pool.

Garbage Collection

This is great, but without garbage collection, the pool will grow unbounded as the user scrolls through their feed. This is where the pool GC (garbage collector) comes in.

Add this action to your model, and pass into poolGC any property where these posts might have a reference.

.actions((store) => ({
  gc() {
    store.poolGC([
      store.feed,
      store.currentPost,
      // perhaps some other store as well?
      // search results is a common one
      store.searchStore.filteredPosts,
      // for nested references, you can spread a map, like so:
      ...store.categories.map(cat => cat.posts)
    ])
  }
}))

The GC is pretty fast, but you might not need to run it after every action. Generally, you'll run the GC anytime you do a larger refresh of your data. You can also run it anytime you remove or replace items from a list or property. Or, if you want, you could just run it every so often on a timer. It's up to you and what your project needs.

I recommend mainly doing it after a refresh.

.actions((store) => ({
  setFeed(newPosts) {
    const posts = store.addAllToPool(newPosts)
    store.feed.replace(posts)
    store.gc()
  },
}))

Limitations

Currently, you can only have one reference pool per store. So, if you want more than one reference pool, just make additional stores per model type.

Example:

const FeedStore = types
  .model("FeedStore", {
    pool: types.array(PostModel),
    feed: types.array(types.reference(PostModel)),
  })
  .extend(withReferencePool(PostModel))

const UserStore = types
  .model("UserStore", {
    pool: types.array(UserModel),
    users: types.array(types.reference(UserModel)),
  })
  .extend(withReferencePool(UserModel))

const RootStore = types.model("RootStore", {
  feedStore: FeedStore,
  userStore: UserStore,
})

You can use a pool across multiple stores; just make sure you pass all relevant references in those other stores into your gc action.

API Reference

withReferencePool(ModelType)

This is an MST extension that takes an argument of the entity type that you will be storing in your reference pool.

import { types } from "mobx-state-tree"
import { withReferencePool } from "mst-reference-pool'
import { PostModel } from "./post"

const RootStore = types.model("RootStore", {
  pool: types.array(PostModel)
})
.extend(withReferencePool(PostModel))

addToPool(instanceSnapshot)

This is an action on your extended store that adds an instance to the pool. If an instance already exists with that identifier in the pool, it will run applySnapshot to the existing instance instead of making a duplicate. Think of it as an add or update action.

It will return the MST instance that it creates or updates.

// ...
.actions((store) => ({
  addPost(newPost) {
    const post = store.addToPool(newPost)
    // add reference to it somewhere?
    store.posts.push(post)
    store.currentPost = post
  }
}))

addAllToPool(instanceSnapshots)

This is an action on your extended store that adds multiple instances to the pool. If an instance already exists with that identifier in the pool, it will run applySnapshot to the existing instance instead of making a duplicate. Think of it as an add or update action for multiple items.

It will return an array of the MST instances that it creates or updates.

poolGC([ ...listOfReferences ])

This is an action on your extended store that garbage collects instances in the pool that do not have any living references left.

You need to provide any references, since those could live anywhere on the tree. These can be single references or arrays of references.

I recommend creating a gc action on your store that calls this action and passes in all references.

.actions((store) => ({
  gc() {
    store.poolGC([
      store.feed,
      store.currentPost,
      // perhaps some other store as well?
      // search results is a common one
      store.searchStore.filteredPosts
    ])
  }
}))

Troubleshooting / Tips

  1. Make sure you have a pool in the store that is an array of the model type you want to store
  2. Make sure other properties are references or safeReferences
  3. Make sure to run the GC regularly (see the Garbage Collection section above)
  4. Feel free to join the Infinite Red Community to ask questions in our #mobx-state-tree channel

License

This project is copyright 2021 by Infinite Red, Inc., and licensed under the MIT license.

Further Information

  • Learn about MobX-State-Tree
  • Check out the original live streams where Jamon Holmgren built the first version of this: Links coming & Soon
  • Learn more about Infinite Red
  • Join Jamon on Mondays, Wednesdays, and Fridays on his Twitch stream to hang out while he works on React Native, MobX-State-Tree, and more!