Skip to content

Instantly share code, notes, and snippets.

@mweststrate
Last active May 25, 2022 00:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mweststrate/f27350b3b0a6bea970edd1ee2371a61d to your computer and use it in GitHub Desktop.
Save mweststrate/f27350b3b0a6bea970edd1ee2371a61d to your computer and use it in GitHub Desktop.
React AMA - Michel Weststrate / MobX
// Parameterized computed views:
// Create computed's and store them in a cache
import { observable, computed } from "mobx"
class Todos {
@observable todos = []
private todosByUserCache = new Map()
getAllTodosByUser(userId) {
if (this.todosByUserCache.has(userId))
return this.todosByUserCache.get(userId).get()
const computedFilter = computed(() => this.todos.filter(todo => todo.user === userId))
this.todosByUserCache.set(userId, computedFilter)
return todosByUserCache.get()
}
}
// --- App.js ---
class App {
@observable todos = []
}
export const app = new App()
// ---- vs ----
export class App {
@observable todos = []
}
// =========================================
// --- App1.test.js
import { app } from "./app"
describe("app", () => {
beforeEach(() => {
app.reset() // error prone, there are might be a lot of complex things to reset!
})
test("some test", () => {
app.loadTodos()
expect(app.todos).toMatchSnapshot()
})
})
// ---- vs ----
import { App } from "./app"
test("some test", () => {
const app = new App() // no singleton, every app in every test is pristine
app.loadTodos()
expect(app.todos).toMatchSnapshot()
})
// =========================================
// Using singletons in React
// --- MyComponent.jsx ---
import { app } from "../stores/App"
// simple, app has the todos and the component will react to them
export const Todos = observer(() =>
<ul>
{app.todos.map(todo => <Todo key={todo.id} todo={todo} />)}
</ul
)
// ---- vs ----
// have to make sure an instance of App was provided using Provider higher in the component tree
export const Todos = inject("app")(observer(({app}) =>
<ul>
{app.todos.map(todo => <Todo key={todo.id} todo={todo} />)}
</ul
))
// Organizing stores: build a tree, where access to siblings goes trough the parent
// n.b. these patterns are enforced in to MST!
class App {
constructor() {
this.todos = new Todos(this)
this.authStore = new AuthStore(this)
}
}
class Todos {
constructor(private app: App) {
}
load() {
// Access other stores trough parent
if (this.app.authStore.authenticated) {
// load todos
}
}
}
class Todo {
// Don't store a direct reference, rather use computeds based on id
// This makes sure that if the user in it's store is replaced, it is reflected
// in this todo
@observable userId
constructor(private todoStore) { }
@computed
get user() {
return this.todoStore.app.userStore.getUser(this.userId)
}
set user(user) {
this.userId = user ? user.id : undefined
}
}
// How to test individual stores?
// 1. On small projects, don't, always create App, it's cheap!
// 2. On bigger projects, use a dependency injection library rather than passing everything to through the constructor
// See also: https://hackernoon.com/how-to-decouple-state-and-ui-a-k-a-you-dont-need-componentwillmount-cc90b787aa37
@mweststrate
Copy link
Author

@aldis-ameriks
Copy link

aldis-ameriks commented Mar 14, 2019

Do I need to store the App reference in the store?

class Todos {
  constructor(private app: App) {
    this.app = app;
  }

  load() {
    // Access other stores trough parent
    if (this.app.authStore.authenticated) {
      // load todos
    }
  }
}

@mweststrate
Copy link
Author

mweststrate commented Mar 18, 2019

@aldis-ameriks constructor(private app: App) is a typescript shorthand for constructor(app) { this.app = app }, so, yes, which is done in the example :)

@luna-zhao-8
Copy link

Will authenticated be reacted if the authStore changes this value? Looks like it wouldn't. Should I use reaction to observe the values from other store?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment