MobX-state-tree

MobX-state-tree

  • Documentation
  • TypeDocs
  • Sponsor
  • GitHub

›Recipes

Introduction

  • Welcome to MobX-State-Tree!
  • Installation
  • Getting Started Tutorial
  • Examples
  • Overview & Philosophy

Basic Concepts

  • Types, models, trees & state
  • Actions
  • Derived values
  • React and MST
  • Snapshots
  • Identifiers and references
  • Asynchronous actions

Advanced Concepts

  • Patches
  • Listening to changes
  • Dependency Injection
  • Middleware
  • Reconciliation
  • Volatile state

API Overview

  • Types overview
  • API overview
  • Lifecycle hooks overview

Tips

  • Talks & Blogs
  • Frequently Asked Questions
  • TypeScript and MST
  • Circular dependencies
  • Simulating inheritance
  • Using snapshots as values
  • Miscellaneous Tips

Compare

  • React Context vs. MobX-State-Tree

Recipes

  • Auto-Generated Property Setter Actions
  • Pre-built Form Types with MST Form Type
  • Manage Asynchronous Data with mst-query
Edit

Auto-Generated Property Setter Actions

This recipe was originally developed by Infinite Red.

If you want to modify your MobX-State-Tree model properties, you usually have to write one setter per-property. In a model with two fields, that looks like this:

import { types } from "mobx-state-tree"

const UserModel = types
  .model("User", {
    name: types.string,
    age: types.number
  })
  .actions((self) => ({
    setName(newName: string) {
      self.name = newName
    },
    setAge(newAge: number) {
      self.age = newAge
    }
  }))

As your model grows in size and complexity, these setter actions can be tedious to write, and increase your source file size, making it harder to read through the actual logic of your model.

You can write a generic action in your model, like this:

import { types, SnapshotIn } from "mobx-state-tree"
const UserModel = types
  .model("User", {
    name: types.string,
    age: types.number
  })
  .actions((self) => ({
    setProp<K extends keyof SnapshotIn<typeof self>, V extends SnapshotIn<typeof self>[K]>(
      field: K,
      newValue: V
    ) {
      self[field] = newValue
    }
  }))
const user = UserModel.create({ name: "Jamon", age: 40 })
user.setProp("name", "Joe") // all good!
// typescript will error, like it's supposed to
user.setProp("age", "shouldn't work")

Or, if you want to extract that for easier reuse across different models, you can write a helper, like this:

import { IStateTreeNode, SnapshotIn } from "mobx-state-tree"

// This custom type helps TS know what properties can be modified by our returned function. It excludes actions and views, but still correctly infers model properties for auto-complete and type safety.
type OnlyProperties<T> = {
  [K in keyof SnapshotIn<T>]: K extends keyof T ? T[K] : never
}

/**
 * If you include this in your model in an action() block just under your props,
 * it'll allow you to set property values directly while retaining type safety
 * and also is executed in an action. This is useful because often you find yourself
 * making a lot of repetitive setter actions that only update one prop.
 *
 * E.g.:
 *
 *  const UserModel = types.model("User")
 *    .props({
 *      name: types.string,
 *      age: types.number
 *    })
 *    .actions(withSetPropAction)
 *
 *   const user = UserModel.create({ name: "Jamon", age: 40 })
 *
 *   user.setProp("name", "John") // no type error
 *   user.setProp("age", 30)      // no type error
 *   user.setProp("age", "30")    // type error -- must be number
 */
export const withSetPropAction = <T extends IStateTreeNode>(mstInstance: T) => ({
  setProp<K extends keyof OnlyProperties<T>, V extends SnapshotIn<T>[K]>(field: K, newValue: V) {
    ;(mstInstance as T & OnlyProperties<T>)[field] = newValue
  }
})

You can use the helper in a model like so:

import { t } from "mobx-state-tree"
import { withSetPropAction } from "./withSetPropAction"

const Person = t
  .model("Person", {
    name: t.string
  })
  .views((self) => ({
    get lowercaseName() {
      return self.name.toLowerCase()
    }
  }))
  .actions((self) => ({
    setName(name: string) {
      self.name = name
    }
  }))
  .actions(withSetPropAction)

const you = Person.create({
  name: "your name"
})

you.setProp("name", "Another Name")

// These will all trigger runtime errors. They are included to demonstrate TS support for
// withSetPropAction.
try {
  // @ts-expect-error - this should error because it's the wrong type for name.
  you.setProp("name", 123)
  // @ts-expect-error - this should error since 'nah' is not a property.
  you.setProp("nah", 123)
  // @ts-expect-error - we cannot set views like we can with properties.
  you.setProp("lowercaseName", "your name")
  // @ts-expect-error - we cannot set actions like we can with properties.
  you.setProp("setName", "your name")
} catch (e) {
  console.error(e)
}

See this working in CodeSandbox.

This is a type-safe way to reduce boilerplate and make your MobX-State-Tree models more readable.

← React Context vs. MobX-State-TreePre-built Form Types with MST Form Type →
MobX-state-tree
Docs
Getting StartedBasic conceptsAdvanced conceptsAPI Reference
Community
GitHub DiscussionsStack Overflow
More
BlogGitHubStar
Facebook Open Source
Copyright © 2025 Michel Weststrate