1
ReduxRedux
Raise your hand if you used in production...Raise your hand if you used in production...
2
MobXMobX
Raise your hand if you used in production...Raise your hand if you used in production...
3
React.createContextReact.createContext
Do you want to experiment with justDo you want to experiment with just
I hope I'll change your idea!
I hope I'll change your idea!
4
Ego SlideEgo Slide
@mattiamanzati
5
What does anWhat does an
application needs?application needs?
6
What does anWhat does an
application needs?application needs?
STORE ITS STATE
6
What does anWhat does an
application needs?application needs?
STORE ITS STATEMODIFY ITS STATE
6
What does anWhat does an
application needs?application needs?
STORE ITS STATEMODIFY ITS STATE
VIEW ITS STATE
AGGREGATED
6
What does anWhat does an
application needs?application needs?
STORE ITS STATEMODIFY ITS STATE
VIEW ITS STATE
AGGREGATED
PERFORM
SIDE EFFECTS
6
...JavaScript?...JavaScript?
7
...JavaScript?...JavaScript?
{ 
name: "work", 
done: false 
}
7
...JavaScript?...JavaScript?
{ 
name: "work", 
done: false 
}
onClick = () =>{ 
todo.done = true 
}
7
...JavaScript?...JavaScript?
{ 
name: "work", 
done: false 
}
onClick = () =>{ 
todo.done = true 
}
get totalCount(){ 
return this.todos.length 
}
7
...JavaScript?...JavaScript?
{ 
name: "work", 
done: false 
}
onClick = () =>{ 
todo.done = true 
}
get totalCount(){ 
return this.todos.length 
}
???
7
MobXMobX
8
MobXMobX
Reactive library
8
MobXMobX
Reactive library
No concept of stream
8
MobXMobX
Reactive library
No concept of stream
Uses Proxies (ES6+) or Atoms (ES5-)
8
ObservablesObservables
ActionsActions
9
ObservablesObservables
Store your application state
ActionsActions
9
ObservablesObservables
Store your application state
State may change over time
ActionsActions
9
ObservablesObservables
Store your application state
State may change over time
Observable instance may be the same
ActionsActions
9
ObservablesObservables
Store your application state
State may change over time
Observable instance may be the same
ActionsActions
Change observables state value
9
ObservablesObservables
Store your application state
State may change over time
Observable instance may be the same
ActionsActions
Change observables state value
Can be triggered from UI or side effects
9
ComputedsComputeds
ReactionsReactions
10
ComputedsComputeds
Derived state data
ReactionsReactions
10
ComputedsComputeds
Derived state data
Automatically updated synchronusly
ReactionsReactions
10
ComputedsComputeds
Derived state data
Automatically updated synchronusly
Always up to date with current
observable state
ReactionsReactions
10
ComputedsComputeds
Derived state data
Automatically updated synchronusly
Always up to date with current
observable state
ReactionsReactions
Trigger functions when a condition changes
10
ComputedsComputeds
Derived state data
Automatically updated synchronusly
Always up to date with current
observable state
ReactionsReactions
Trigger functions when a condition changes
UI is a reaction of the store state
10
A successful pattern inA successful pattern in
history!history!
11
12
Excel is Reactive!Excel is Reactive!
13
Excel is Reactive!Excel is Reactive!
CELLS
13
Excel is Reactive!Excel is Reactive!
CELLSUSER INTERACTION
13
Excel is Reactive!Excel is Reactive!
CELLSUSER INTERACTION
FORMULAS
13
Excel is Reactive!Excel is Reactive!
CELLSUSER INTERACTION
FORMULAS
SCREEN UPDATES
13
Let's start!Let's start!
A classic example: TODO ListsA classic example: TODO Lists
14
class App extends React.Component {
// observable values
currentText = "";
todos = [];
// actions
addTodo = () => {
this.todos.push({ name: this.currentText, done: false });
this.currentText = "";
};
toggleTodo = todo => {
todo.done = !todo.done;
};
setCurrentText = text => {
this.currentText = text;
};
// computed
get pendingCount() {
return this.todos.filter(todo => !todo.done).length;
}
// ...
} 15
class App extends React.Component {
// observable values
currentText = "";
todos = [];
// actions
addTodo = () => {
this.todos.push({ name: this.currentText, done: false });
this.currentText = "";
};
toggleTodo = todo => {
todo.done = !todo.done;
};
setCurrentText = text => {
this.currentText = text;
};
// computed
get pendingCount() {
return this.todos.filter(todo => !todo.done).length;
}
// ...
} 15
class App extends React.Component {
// observable values
currentText = "";
todos = [];
// actions
addTodo = () => {
this.todos.push({ name: this.currentText, done: false });
this.currentText = "";
};
toggleTodo = todo => {
todo.done = !todo.done;
};
setCurrentText = text => {
this.currentText = text;
};
// computed
get pendingCount() {
return this.todos.filter(todo => !todo.done).length;
}
// ...
} 15
class App extends React.Component {
// observable values
currentText = "";
todos = [];
// actions
addTodo = () => {
this.todos.push({ name: this.currentText, done: false });
this.currentText = "";
};
toggleTodo = todo => {
todo.done = !todo.done;
};
setCurrentText = text => {
this.currentText = text;
};
// computed
get pendingCount() {
return this.todos.filter(todo => !todo.done).length;
}
// ...
} 15
class App extends React.Component {
// ...
// render observer
render() {
return (
<div className="App">
<h1>TODOs</h1>
<input
type="text"
value={this.currentText}
onChange={e => this.setCurrentText(e.target.value)}
/>
<button onClick={this.addTodo}>Add</button>
<ul>
{this.todos.map(item => (
<li onClick={() => this.toggleTodo(item)}>
{item.name} {item.done ? <i>DONE!</i> : null}
</li>
))}
</ul>
<p>There are {this.pendingCount} pending todos.</p>
</div>
);
16
class App extends React.Component {
// ...
// render observer
render() {
return (
<div className="App">
<h1>TODOs</h1>
<input
type="text"
value={this.currentText}
onChange={e => this.setCurrentText(e.target.value)}
/>
<button onClick={this.addTodo}>Add</button>
<ul>
{this.todos.map(item => (
<li onClick={() => this.toggleTodo(item)}>
{item.name} {item.done ? <i>DONE!</i> : null}
</li>
))}
</ul>
<p>There are {this.pendingCount} pending todos.</p>
</div>
);
16
class App extends React.Component {
// ...
}
const MyApp = observer(App);
decorate(App, {
currentText: observable,
todos: observable,
addTodo: action,
toggleTodo: action,
setCurrentText: action,
pendingCount: computed
});
const rootElement = document.getElementById("root");
ReactDOM.render(<MyApp />, rootElement);
17
class App extends React.Component {
// ...
}
const MyApp = observer(App);
decorate(App, {
currentText: observable,
todos: observable,
addTodo: action,
toggleTodo: action,
setCurrentText: action,
pendingCount: computed
});
const rootElement = document.getElementById("root");
ReactDOM.render(<MyApp />, rootElement);
17
Few notes:Few notes:
Component state managed byComponent state managed by
MobX or not?MobX or not?
class App extends React.Component {
@observable
currentText = ""
// ...
}
export default observer(App)
class App extends React.Component {
state = {
currentText: ""
}
// ...
} 18
Few notes:Few notes:
Component state managed byComponent state managed by
MobX or not?MobX or not?
class App extends React.Component {
@observable
currentText = ""
// ...
}
export default observer(App)
19
Few notes:Few notes:
Component state managed byComponent state managed by
MobX or not?MobX or not?
Better optimized than setState
class App extends React.Component {
@observable
currentText = ""
// ...
}
export default observer(App)
19
Few notes:Few notes:
Component state managed byComponent state managed by
MobX or not?MobX or not?
Better optimized than setState
Simpler API than setState
class App extends React.Component {
@observable
currentText = ""
// ...
}
export default observer(App)
19
Few notes:Few notes:
Component state managed byComponent state managed by
MobX or not?MobX or not?
Better optimized than setState
Simpler API than setState
Easier to refactor to a separate store
class App extends React.Component {
@observable
currentText = ""
// ...
}
export default observer(App)
19
Few notes:Few notes:
Decorators are optionalDecorators are optional
class Store {
@observable
todos = []
}
class Store {
todos = []
}
decorate(Store, {
todos: observable
});
20
Few notes:Few notes:
Classes are optionalClasses are optional
class Store {
@observable
todos = []
}
const store = new Store()
const store = observable({
todos: []
})
21
MobX is unopinionatedMobX is unopinionated
22
MobX is unopinionatedMobX is unopinionated
How do I X?
22
MobX is unopinionatedMobX is unopinionated
How do I X?
Should I MVC?
22
MobX is unopinionatedMobX is unopinionated
How do I X?
Should I MVC?
Should I MVVM?
22
MobX is unopinionatedMobX is unopinionated
How do I X?
Should I MVC?
Should I MVVM?
Should I MVP?
22
MobX is unopinionatedMobX is unopinionated
How do I X?
Should I MVC?
Should I MVVM?
Should I MVP?
Should I Flux?
22
MobX is aMobX is a
reactive data libraryreactive data library
23
It works!It works!
24
It works!It works!
Does it scales?
Does it scales?
24
It works!It works!
Is it testable?
Is it testable?
Does it scales?
Does it scales?
24
It works!It works!
Is it testable?
Is it testable?
Mix BL and UI?
Mix BL and UI?
Does it scales?
Does it scales?
24
Don't mix Business Logic & UIDon't mix Business Logic & UI
25
Don't mix Business Logic & UIDon't mix Business Logic & UI
What if we want a React Native app later?
25
Don't mix Business Logic & UIDon't mix Business Logic & UI
What if we want a React Native app later?
What if UI framework changes?
25
Don't mix Business Logic & UIDon't mix Business Logic & UI
What if we want a React Native app later?
What if UI framework changes?
What if we need SSR?
25
Don't mix Business Logic & UIDon't mix Business Logic & UI
What if we want a React Native app later?
What if UI framework changes?
What if we need SSR?
25
Don't mix Business Logic & UIDon't mix Business Logic & UI
class Store {
// observable values
currentText = "";
todos = [];
// actions
addTodo = () => {
this.todos.push({ name: this.currentText, done: false });
this.currentText = "";
};
toggleTodo = todo => { todo.done = !todo.done; };
setCurrentText = text => { this.currentText = text; };
// computed
get pendingCount() {
return this.todos.filter(todo => !todo.done).length;
}
}
Extracting the StoreExtracting the Store
26
class Store {
// ...
}
class App extends React.Component {
// ...
}
const MyApp = observer(Store);
const store = new Store();
const rootElement = document.getElementById("root");
ReactDOM.render(<MyApp store={store} />, rootElement);
Don't mix Business Logic & UIDon't mix Business Logic & UI
Wiring the Store as propWiring the Store as prop
27
import { observer, Provider, inject }
from "mobx-react";
// ...
const MyApp = inject("store")(observer(App));
const store = new Store();
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<MyApp />
</Provider>, rootElement);
Don't mix Business Logic & UIDon't mix Business Logic & UI
Use Provider & InjectUse Provider & Inject
28
import { observer, Provider, inject }
from "mobx-react";
// ...
const MyApp = inject("store")(observer(App));
const store = new Store();
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<MyApp />
</Provider>, rootElement);
Don't mix Business Logic & UIDon't mix Business Logic & UI
Use Provider & InjectUse Provider & Inject
No need to pass down manually your stores
28
import { observer, Provider, inject }
from "mobx-react";
// ...
const MyApp = inject("store")(observer(App));
const store = new Store();
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<MyApp />
</Provider>, rootElement);
Don't mix Business Logic & UIDon't mix Business Logic & UI
Use Provider & InjectUse Provider & Inject
No need to pass down manually your stores
Allows to change store implementation in your tests
28
import { observer, Provider, inject }
from "mobx-react";
// ...
const MyApp = inject("store")(observer(App));
const store = new Store();
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<MyApp />
</Provider>, rootElement);
Don't mix Business Logic & UIDon't mix Business Logic & UI
Use Provider & InjectUse Provider & Inject
No need to pass down manually your stores
Allows to change store implementation in your tests
Only one point of store injections into the views
28
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
STORE
Renders the view and
calls actions on the store
Holds and modify domain state
Holds and modify application state
Implements business logic
Fetch from API
Implements view actions
Aggregates data for the view
29
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
STORE
Renders the view and
calls actions on the store
Holds and modify domain state
Holds and modify application state
Implements business logic
Fetch from API
Implements view actions
Aggregates data for the view
W
ay too m
uch
W
ay too m
uch
things!
things!
29
Domain StateDomain State
WTF is dat?WTF is dat?
30
Domain StateDomain State
WTF is dat?WTF is dat?
Is the domain of your app
in our example, of Todo
30
Domain StateDomain State
WTF is dat?WTF is dat?
Is the domain of your app
in our example, of Todo
Describes the application entities and relations
30
Domain StateDomain State
WTF is dat?WTF is dat?
Is the domain of your app
in our example, of Todo
Describes the application entities and relations
Usually it is persistent and stored somewhere
30
Domain StateDomain State
WTF is dat?WTF is dat?
Is the domain of your app
in our example, of Todo
Describes the application entities and relations
Usually it is persistent and stored somewhere
Not tight with the UI
30
Domain StateDomain State
WTF is dat?WTF is dat?
Is the domain of your app
in our example, of Todo
Describes the application entities and relations
Usually it is persistent and stored somewhere
Not tight with the UI
Highly reusable
30
Domain StateDomain State
first implementationfirst implementation
class Todo {
name = "";
done = false;
}
decorate(Todo, {
name: observable,
done: observable
});
class Store {
// ...
addTodo = () => {
const todo = new Todo();
todo.name = this.currentText;
this.todos.push(todo);
this.currentText = "";
};
}
31
Domain StateDomain State
domain actionsdomain actions
// domain todo model
class Todo {
name = "";
done = false;
toggle(){
this.done = !this.done
}
}
decorate(Todo, {
// ...
toggle: action
});
Observables should be changed trough actions! So it's a
good idea to define dumb actions on our domain model!
32
Domain StateDomain State
let's be more real!let's be more real!
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0; // UHM... is this ok?
// ...
}
// domain user model
class User {
id = 0
name = ""
}
33
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
Difficult access 34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
Difficult access
Denormalized
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
Difficult access
Denormalized
Object graph
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
Difficult access
Denormalized
Object graph
Harder serialization
34
Domain StateDomain State
tree or graph?tree or graph?
// domain todo model
class Todo {
name = "";
done = false;
user_id = 0;
// ...
}
// domain user model
class User {
id = 0
name = ""
}
// domain todo model
class Todo {
name = "";
done = false;
user = new User(0, "");
// ...
}
// domain user model
class User {
id = 0
name = ""
}
Normalized
Object tree
Easily serialize
Difficult access
Denormalized
Object graph
Harder serialization
Easy access 34
Domain StateDomain State
good news everyone!good news everyone!
35
Domain StateDomain State
lets do both!lets do both!
// domain todo model
class Todo {
name = ""
done = false
user_id = 0
get user(){
return store.getUserById(this.user_id) // <- WTF
}
set user(value){
this.user_id = value.id
}
}
decorate(Todo, {
//...
user_id: observable,
user: computed
}) 36
Domain StateDomain State
lets do both!lets do both!
// domain todo model
class Todo {
name = ""
done = false
user_id = 0
get user(){
return store.getUserById(this.user_id) // <- WTF
}
set user(value){
this.user_id = value.id
}
}
decorate(Todo, {
//...
user_id: observable,
user: computed
}) 36
Multiple Store CommunicationMultiple Store Communication
available pattersavailable patters
37
Multiple Store CommunicationMultiple Store Communication
available pattersavailable patters
Singleton instance & require/import
37
Multiple Store CommunicationMultiple Store Communication
available pattersavailable patters
Singleton instance & require/import
Dependency injection framework
37
Multiple Store CommunicationMultiple Store Communication
available pattersavailable patters
Singleton instance & require/import
Dependency injection framework
Root store pattern
37
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
class RootStore {
todoStore = null;
fetch = null;
apiKey = "";
constructor(fetch, apiKey){
this.fetch = fetch
this.apiKey = apiKey
this.todoStore = new Store(this)
}
}
class Todo {
store = null;
constructor(store){ this.store = store }
}
class Store {
rootStore = null;
constructor(rootStore){ this.rootStore = rootStore }
} 38
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
39
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
Central point for each store to
communicate
39
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
Central point for each store to
communicate
Strongly typed
39
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
Central point for each store to
communicate
Strongly typed
Works as dependency root
Can host environment specific variables
39
Multiple Store CommunicationMultiple Store Communication
root store patternroot store pattern
Central point for each store to
communicate
Strongly typed
Works as dependency root
Can host environment specific variables
Very easily testable
39
Multiple Store CommunicationMultiple Store Communication
back to our problemback to our problem
// domain todo model
class Todo {
name = ""
done = false
user_id = 0
store = null;
constructor(store){ this.store = store; }
get user(){
return this.store.rootStore
.userStore.getUserById(this.user_id)
}
set user(value){
this.user_id = value.id
}
}
40
Multiple Store CommunicationMultiple Store Communication
back to our problemback to our problem
import { observer, Provider,
inject } from "mobx-react";
// ...
const MyApp = inject("store")(observer(App));
const store = new RootStore(window.fetch);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<MyApp />
</Provider>, rootElement);
41
Multiple Store CommunicationMultiple Store Communication
back to our problemback to our problem
test("it should restore data from API", async t => {
const fakeFetch = () => Promise.resolve({
data: [{id: 1, name: "Mattia"}]
})
const store = new RootStore(fakeFetch)
await store.userStore.fetchAll()
t.equal(store.userStore.users[0].name, "Mattia")
})
42
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
STORE
Renders the view and
calls actions on the store
Holds domain state
Create and modify domain state
Holds and modify application state
Implements business logic
Fetch from API
Aggregates data for the view
DOMAIN MODEL
43
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
STORE
Renders the view and
calls actions on the store
Holds domain state
Create and modify domain state
Holds and modify application state
Implements business logic
Fetch from API
Aggregates data for the view
DOMAIN MODEL
43
Domain Model SerializationDomain Model Serialization
turning JSON into observablesturning JSON into observables
44
Domain Model SerializationDomain Model Serialization
turning JSON into observablesturning JSON into observables
Our state is an object tree
44
Domain Model SerializationDomain Model Serialization
turning JSON into observablesturning JSON into observables
Our state is an object tree
Unfortunately it's not a plain JS object
44
Domain Model SerializationDomain Model Serialization
turning JSON into observablesturning JSON into observables
Our state is an object tree
Unfortunately it's not a plain JS object
Most APIs provide JSON as intermediate language
44
Domain Model SerializationDomain Model Serialization
turning JSON into observablesturning JSON into observables
Our state is an object tree
Unfortunately it's not a plain JS object
Most APIs provide JSON as intermediate language
We need to provide conversions to serialize or
deserialize our state
44
Domain Model SerializationDomain Model Serialization
deserializationdeserialization
class Todo {
// ...
constructor(store, data){
this.store = store
if(data) Object.assign(this, data)
}
}
45
Domain Model SerializationDomain Model Serialization
serializationserialization
class Todo {
// ...
get toJSON(){
return {
name: this.name,
done: this.done
}
}
}
// ...
decorate(Todo, {
toJSON: computed
})
JSON is just another view of the domain model,
so we can just derive itderive
46
Domain Model SerializationDomain Model Serialization
packagespackages
class User {
@serializable(identifier()) id = 0;
@serializable name = '';
}
class Todo {
@serializable name = '';
@serializable done = false;
@serializable(object(User)) user = null;
}
// You can now deserialize and serialize!
const todo = deserialize(Todo, {
name: 'Hello world',
done: true,
user: { id: 1, name: 'Mattia' }
});
const todoJSON = serialize(todo)
with serializr you get that with tagging your domain models
47
Domain Model SerializationDomain Model Serialization
the deserialization problemthe deserialization problem
class TodoStore {
todos = []
fromCache(){
const cachedData = localStorage.getItem("todos")
|| "[]"
this.todos = JSON.parse(cachedData)
.map(data => new Todo(this, data)
}
getById = id => this.todos
.find(item => item.id === id)
}
decorate(TodoStore, {
fromCache: action
})
deserializing is memory intensive!
48
MobX hidden featureMobX hidden feature
_interceptReads_interceptReads
import {_interceptReads} from "mobx"
const todos = observable.map()
_interceptReads(todos, value => value + "! LOL")
todos.set(1, "Mattia")
console.log(todos.get("1")) // => Mattia! LOL
undocumented (yet there since 2017) mobx feature
allows to transform mobx values while reading
available for objects, arrays, maps and boxed values
49
MobX hidden featureMobX hidden feature
_interceptReads to the rescue!_interceptReads to the rescue!
class TodoStore {
todos = []
_cache = {}
constructor(rootStore){
this.rootStore = rootStore
// ...
_interceptReads(this.todos, this.unboxTodo)
}
unboxTodo = data => {
if(this._cache[data.id]){
return this._cache[data.id]
}
this._cache[data.id] = new Todo(this, data)
return this._cache[data.id]
}
}
50
MobX PerformanceMobX Performance
better performant domain storesbetter performant domain stores
51
MobX PerformanceMobX Performance
better performant domain storesbetter performant domain stores
Use ES6 Maps and lookup by ID when possible
51
MobX PerformanceMobX Performance
better performant domain storesbetter performant domain stores
Use ES6 Maps and lookup by ID when possible
Store JSON in an observable map
51
MobX PerformanceMobX Performance
better performant domain storesbetter performant domain stores
Use ES6 Maps and lookup by ID when possible
Store JSON in an observable map
Use _interceptReads to perform lazy deserialization
51
MobX PerformanceMobX Performance
better performant domain storesbetter performant domain stores
Use ES6 Maps and lookup by ID when possible
Store JSON in an observable map
Use _interceptReads to perform lazy deserialization
Implement ID -> name without deserialization
51
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
REPOSITORY
Renders the view and
calls actions on the store
Holds domain state
Create and modify domain state
Fetch from API
Holds and modify application state
Implements business logic
Aggregates data for the view
STORE
DOMAIN MODEL
52
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
REPOSITORY
Renders the view and
calls actions on the store
Holds domain state
Create and modify domain state
Fetch from API
Holds and modify application state
Implements business logic
Aggregates data for the view
STORE
DOMAIN MODEL
52
class Overview {
rootStore = null
currentText = ""
constructor(rootStore){ this.rootStore = rootStore }
get todos(){
// perform sorting and pagining here
return this.rootStore.todoStore.todos
}
handleAddClicked(){
this.rootStore.todoStore.addTodo(this.currentText)
this.currentText = ""
}
}
decorate(Router, {
todos: computed,
currentText: observable,
handleAddClicked: action
})
53
Services & PresentersServices & Presenters
routingrouting
class Router {
uri = "/"
page = null
// ...
constructor(rootStore){
reaction(() => this.uri, uri => this.parseUri(uri))
}
openOverview(){
this.page = { name: "overview", data: { project_id: 1 }}
}
}
decorate(Router, {
uri: observable,
page: observable
})
54
Services & PresentersServices & Presenters
routingrouting
class Router {
uri = "/"
page = null
// ...
constructor(rootStore){
reaction(() => this.uri, uri => this.parseUri(uri))
}
openOverview(){
this.page = { name: "overview", data: { project_id: 1 }}
}
}
decorate(Router, {
uri: observable,
page: observable
}) Where load that data?
Where load that data?
54
Services & PresentersServices & Presenters
view presenterview presenter
class Router {
uri = "/"
page = null
constructor(rootStore){
this.page = this.loadHome()
reaction(() => this.uri, uri => this.parseUri(uri))
}
openOverview(){
this.page = fromPromise(
this.overviewStore.fetchData()
.then(data =>
Promise.resolve({ name: "overview", data })
)
)
}
}
55
Services & PresentersServices & Presenters
view presenterview presenter
class Router {
uri = "/"
page = null
constructor(rootStore){
this.page = this.loadHome()
reaction(() => this.uri, uri => this.parseUri(uri))
}
openOverview(){
this.page = fromPromise(
this.overviewStore.fetchData()
.then(data =>
Promise.resolve({ name: "overview", data })
)
)
}
}
55
Services & PresentersServices & Presenters
fromPromisefromPromise
class Router {
uri = "/"
page = null
constructor(rootStore){
this.page = this.loadHome()
reaction(() => this.uri, uri => this.parseUri(uri))
}
openOverview(){
this.page = fromPromise(
this.overviewStore.fetchData()
.then(data =>
Promise.resolve({ name: "overview", data })
)
)
}
}
56
MobX Async TipsMobX Async Tips
reactive finite state machinesreactive finite state machines
<div>
{ this.page.case({
pending: () => "loading",
rejected: (e) => "error: " + e,
fulfilled: ({name, data}) => {
switch(name){
case "home": return <HomePage data={data} />;
case "overview": return <Overview data={data} />;
}
}
})}
</div>
57
Real-World ArchitectureReal-World Architecture
Current State RecapCurrent State Recap
VIEW
REPOSITORY
Renders the view and
calls actions on the store
Holds domain state
Create and modify domain state
Fetch from API
Holds and modify application state
Implements business logic
SERVICE
DOMAIN MODEL
PRESENTER
Aggregates data for the view
Call actions on the services
58
Today's RecapToday's Recap
59
Today's RecapToday's Recap
1. Use Redux if you're paid by the hour
59
Today's RecapToday's Recap
1. Use Redux if you're paid by the hour
2. Find your minimal state
59
Today's RecapToday's Recap
1. Use Redux if you're paid by the hour
2. Find your minimal state
3. Derive whatever is possible
59
Today's RecapToday's Recap
1. Use Redux if you're paid by the hour
2. Find your minimal state
3. Derive whatever is possible
4. Think as you're building for any UI environment
59
Today's RecapToday's Recap
1. Use Redux if you're paid by the hour
2. Find your minimal state
3. Derive whatever is possible
4. Think as you're building for any UI environment
5. Always experiment and have fun!
59
Want something opinionated?Want something opinionated?
1. Define store shape
2. Automatic serialization/deserialization
3. Implements both a immutable & mutable store
4. Defines a stricter architecture
5. Time travelling
discover mobx-state-treediscover mobx-state-tree
60
Not into mutable things?Not into mutable things?
stay here for the next talk!stay here for the next talk!
61
Thanks for your time!Thanks for your time!
Questions?Questions?
 
@MattiaManzati - slides.com/mattiamanzati
62
ReferencesReferences
 
https://slides.com/mattiamanzati/real-world-
mobx
http://mobx-patterns.surge.sh/
https://medium.com/@mweststrate/ui-as-an-
afterthought-26e5d2bb24d6
https://github.com/mobxjs/mobx
https://github.com/mobxjs/mobx-react
https://github.com/mobxjs/mobx-state-tree
63

Mattia Manzati - Real-World MobX Project Architecture - Codemotion Rome 2019

  • 1.
  • 2.
    ReduxRedux Raise your handif you used in production...Raise your hand if you used in production... 2
  • 3.
    MobXMobX Raise your handif you used in production...Raise your hand if you used in production... 3
  • 4.
    React.createContextReact.createContext Do you wantto experiment with justDo you want to experiment with just I hope I'll change your idea! I hope I'll change your idea! 4
  • 5.
  • 6.
    What does anWhatdoes an application needs?application needs? 6
  • 7.
    What does anWhatdoes an application needs?application needs? STORE ITS STATE 6
  • 8.
    What does anWhatdoes an application needs?application needs? STORE ITS STATEMODIFY ITS STATE 6
  • 9.
    What does anWhatdoes an application needs?application needs? STORE ITS STATEMODIFY ITS STATE VIEW ITS STATE AGGREGATED 6
  • 10.
    What does anWhatdoes an application needs?application needs? STORE ITS STATEMODIFY ITS STATE VIEW ITS STATE AGGREGATED PERFORM SIDE EFFECTS 6
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    MobXMobX Reactive library No conceptof stream Uses Proxies (ES6+) or Atoms (ES5-) 8
  • 20.
  • 21.
  • 22.
    ObservablesObservables Store your applicationstate State may change over time ActionsActions 9
  • 23.
    ObservablesObservables Store your applicationstate State may change over time Observable instance may be the same ActionsActions 9
  • 24.
    ObservablesObservables Store your applicationstate State may change over time Observable instance may be the same ActionsActions Change observables state value 9
  • 25.
    ObservablesObservables Store your applicationstate State may change over time Observable instance may be the same ActionsActions Change observables state value Can be triggered from UI or side effects 9
  • 26.
  • 27.
  • 28.
    ComputedsComputeds Derived state data Automaticallyupdated synchronusly ReactionsReactions 10
  • 29.
    ComputedsComputeds Derived state data Automaticallyupdated synchronusly Always up to date with current observable state ReactionsReactions 10
  • 30.
    ComputedsComputeds Derived state data Automaticallyupdated synchronusly Always up to date with current observable state ReactionsReactions Trigger functions when a condition changes 10
  • 31.
    ComputedsComputeds Derived state data Automaticallyupdated synchronusly Always up to date with current observable state ReactionsReactions Trigger functions when a condition changes UI is a reaction of the store state 10
  • 32.
    A successful patterninA successful pattern in history!history! 11
  • 33.
  • 34.
    Excel is Reactive!Excelis Reactive! 13
  • 35.
    Excel is Reactive!Excelis Reactive! CELLS 13
  • 36.
    Excel is Reactive!Excelis Reactive! CELLSUSER INTERACTION 13
  • 37.
    Excel is Reactive!Excelis Reactive! CELLSUSER INTERACTION FORMULAS 13
  • 38.
    Excel is Reactive!Excelis Reactive! CELLSUSER INTERACTION FORMULAS SCREEN UPDATES 13
  • 39.
    Let's start!Let's start! Aclassic example: TODO ListsA classic example: TODO Lists 14
  • 40.
    class App extendsReact.Component { // observable values currentText = ""; todos = []; // actions addTodo = () => { this.todos.push({ name: this.currentText, done: false }); this.currentText = ""; }; toggleTodo = todo => { todo.done = !todo.done; }; setCurrentText = text => { this.currentText = text; }; // computed get pendingCount() { return this.todos.filter(todo => !todo.done).length; } // ... } 15
  • 41.
    class App extendsReact.Component { // observable values currentText = ""; todos = []; // actions addTodo = () => { this.todos.push({ name: this.currentText, done: false }); this.currentText = ""; }; toggleTodo = todo => { todo.done = !todo.done; }; setCurrentText = text => { this.currentText = text; }; // computed get pendingCount() { return this.todos.filter(todo => !todo.done).length; } // ... } 15
  • 42.
    class App extendsReact.Component { // observable values currentText = ""; todos = []; // actions addTodo = () => { this.todos.push({ name: this.currentText, done: false }); this.currentText = ""; }; toggleTodo = todo => { todo.done = !todo.done; }; setCurrentText = text => { this.currentText = text; }; // computed get pendingCount() { return this.todos.filter(todo => !todo.done).length; } // ... } 15
  • 43.
    class App extendsReact.Component { // observable values currentText = ""; todos = []; // actions addTodo = () => { this.todos.push({ name: this.currentText, done: false }); this.currentText = ""; }; toggleTodo = todo => { todo.done = !todo.done; }; setCurrentText = text => { this.currentText = text; }; // computed get pendingCount() { return this.todos.filter(todo => !todo.done).length; } // ... } 15
  • 44.
    class App extendsReact.Component { // ... // render observer render() { return ( <div className="App"> <h1>TODOs</h1> <input type="text" value={this.currentText} onChange={e => this.setCurrentText(e.target.value)} /> <button onClick={this.addTodo}>Add</button> <ul> {this.todos.map(item => ( <li onClick={() => this.toggleTodo(item)}> {item.name} {item.done ? <i>DONE!</i> : null} </li> ))} </ul> <p>There are {this.pendingCount} pending todos.</p> </div> ); 16
  • 45.
    class App extendsReact.Component { // ... // render observer render() { return ( <div className="App"> <h1>TODOs</h1> <input type="text" value={this.currentText} onChange={e => this.setCurrentText(e.target.value)} /> <button onClick={this.addTodo}>Add</button> <ul> {this.todos.map(item => ( <li onClick={() => this.toggleTodo(item)}> {item.name} {item.done ? <i>DONE!</i> : null} </li> ))} </ul> <p>There are {this.pendingCount} pending todos.</p> </div> ); 16
  • 46.
    class App extendsReact.Component { // ... } const MyApp = observer(App); decorate(App, { currentText: observable, todos: observable, addTodo: action, toggleTodo: action, setCurrentText: action, pendingCount: computed }); const rootElement = document.getElementById("root"); ReactDOM.render(<MyApp />, rootElement); 17
  • 47.
    class App extendsReact.Component { // ... } const MyApp = observer(App); decorate(App, { currentText: observable, todos: observable, addTodo: action, toggleTodo: action, setCurrentText: action, pendingCount: computed }); const rootElement = document.getElementById("root"); ReactDOM.render(<MyApp />, rootElement); 17
  • 48.
    Few notes:Few notes: Componentstate managed byComponent state managed by MobX or not?MobX or not? class App extends React.Component { @observable currentText = "" // ... } export default observer(App) class App extends React.Component { state = { currentText: "" } // ... } 18
  • 49.
    Few notes:Few notes: Componentstate managed byComponent state managed by MobX or not?MobX or not? class App extends React.Component { @observable currentText = "" // ... } export default observer(App) 19
  • 50.
    Few notes:Few notes: Componentstate managed byComponent state managed by MobX or not?MobX or not? Better optimized than setState class App extends React.Component { @observable currentText = "" // ... } export default observer(App) 19
  • 51.
    Few notes:Few notes: Componentstate managed byComponent state managed by MobX or not?MobX or not? Better optimized than setState Simpler API than setState class App extends React.Component { @observable currentText = "" // ... } export default observer(App) 19
  • 52.
    Few notes:Few notes: Componentstate managed byComponent state managed by MobX or not?MobX or not? Better optimized than setState Simpler API than setState Easier to refactor to a separate store class App extends React.Component { @observable currentText = "" // ... } export default observer(App) 19
  • 53.
    Few notes:Few notes: Decoratorsare optionalDecorators are optional class Store { @observable todos = [] } class Store { todos = [] } decorate(Store, { todos: observable }); 20
  • 54.
    Few notes:Few notes: Classesare optionalClasses are optional class Store { @observable todos = [] } const store = new Store() const store = observable({ todos: [] }) 21
  • 55.
    MobX is unopinionatedMobXis unopinionated 22
  • 56.
    MobX is unopinionatedMobXis unopinionated How do I X? 22
  • 57.
    MobX is unopinionatedMobXis unopinionated How do I X? Should I MVC? 22
  • 58.
    MobX is unopinionatedMobXis unopinionated How do I X? Should I MVC? Should I MVVM? 22
  • 59.
    MobX is unopinionatedMobXis unopinionated How do I X? Should I MVC? Should I MVVM? Should I MVP? 22
  • 60.
    MobX is unopinionatedMobXis unopinionated How do I X? Should I MVC? Should I MVVM? Should I MVP? Should I Flux? 22
  • 61.
    MobX is aMobXis a reactive data libraryreactive data library 23
  • 62.
  • 63.
    It works!It works! Doesit scales? Does it scales? 24
  • 64.
    It works!It works! Isit testable? Is it testable? Does it scales? Does it scales? 24
  • 65.
    It works!It works! Isit testable? Is it testable? Mix BL and UI? Mix BL and UI? Does it scales? Does it scales? 24
  • 66.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI 25
  • 67.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI What if we want a React Native app later? 25
  • 68.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI What if we want a React Native app later? What if UI framework changes? 25
  • 69.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI What if we want a React Native app later? What if UI framework changes? What if we need SSR? 25
  • 70.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI What if we want a React Native app later? What if UI framework changes? What if we need SSR? 25
  • 71.
    Don't mix BusinessLogic & UIDon't mix Business Logic & UI class Store { // observable values currentText = ""; todos = []; // actions addTodo = () => { this.todos.push({ name: this.currentText, done: false }); this.currentText = ""; }; toggleTodo = todo => { todo.done = !todo.done; }; setCurrentText = text => { this.currentText = text; }; // computed get pendingCount() { return this.todos.filter(todo => !todo.done).length; } } Extracting the StoreExtracting the Store 26
  • 72.
    class Store { //... } class App extends React.Component { // ... } const MyApp = observer(Store); const store = new Store(); const rootElement = document.getElementById("root"); ReactDOM.render(<MyApp store={store} />, rootElement); Don't mix Business Logic & UIDon't mix Business Logic & UI Wiring the Store as propWiring the Store as prop 27
  • 73.
    import { observer,Provider, inject } from "mobx-react"; // ... const MyApp = inject("store")(observer(App)); const store = new Store(); const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <MyApp /> </Provider>, rootElement); Don't mix Business Logic & UIDon't mix Business Logic & UI Use Provider & InjectUse Provider & Inject 28
  • 74.
    import { observer,Provider, inject } from "mobx-react"; // ... const MyApp = inject("store")(observer(App)); const store = new Store(); const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <MyApp /> </Provider>, rootElement); Don't mix Business Logic & UIDon't mix Business Logic & UI Use Provider & InjectUse Provider & Inject No need to pass down manually your stores 28
  • 75.
    import { observer,Provider, inject } from "mobx-react"; // ... const MyApp = inject("store")(observer(App)); const store = new Store(); const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <MyApp /> </Provider>, rootElement); Don't mix Business Logic & UIDon't mix Business Logic & UI Use Provider & InjectUse Provider & Inject No need to pass down manually your stores Allows to change store implementation in your tests 28
  • 76.
    import { observer,Provider, inject } from "mobx-react"; // ... const MyApp = inject("store")(observer(App)); const store = new Store(); const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <MyApp /> </Provider>, rootElement); Don't mix Business Logic & UIDon't mix Business Logic & UI Use Provider & InjectUse Provider & Inject No need to pass down manually your stores Allows to change store implementation in your tests Only one point of store injections into the views 28
  • 77.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW STORE Renders the view and calls actions on the store Holds and modify domain state Holds and modify application state Implements business logic Fetch from API Implements view actions Aggregates data for the view 29
  • 78.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW STORE Renders the view and calls actions on the store Holds and modify domain state Holds and modify application state Implements business logic Fetch from API Implements view actions Aggregates data for the view W ay too m uch W ay too m uch things! things! 29
  • 79.
    Domain StateDomain State WTFis dat?WTF is dat? 30
  • 80.
    Domain StateDomain State WTFis dat?WTF is dat? Is the domain of your app in our example, of Todo 30
  • 81.
    Domain StateDomain State WTFis dat?WTF is dat? Is the domain of your app in our example, of Todo Describes the application entities and relations 30
  • 82.
    Domain StateDomain State WTFis dat?WTF is dat? Is the domain of your app in our example, of Todo Describes the application entities and relations Usually it is persistent and stored somewhere 30
  • 83.
    Domain StateDomain State WTFis dat?WTF is dat? Is the domain of your app in our example, of Todo Describes the application entities and relations Usually it is persistent and stored somewhere Not tight with the UI 30
  • 84.
    Domain StateDomain State WTFis dat?WTF is dat? Is the domain of your app in our example, of Todo Describes the application entities and relations Usually it is persistent and stored somewhere Not tight with the UI Highly reusable 30
  • 85.
    Domain StateDomain State firstimplementationfirst implementation class Todo { name = ""; done = false; } decorate(Todo, { name: observable, done: observable }); class Store { // ... addTodo = () => { const todo = new Todo(); todo.name = this.currentText; this.todos.push(todo); this.currentText = ""; }; } 31
  • 86.
    Domain StateDomain State domainactionsdomain actions // domain todo model class Todo { name = ""; done = false; toggle(){ this.done = !this.done } } decorate(Todo, { // ... toggle: action }); Observables should be changed trough actions! So it's a good idea to define dumb actions on our domain model! 32
  • 87.
    Domain StateDomain State let'sbe more real!let's be more real! // domain todo model class Todo { name = ""; done = false; user_id = 0; // UHM... is this ok? // ... } // domain user model class User { id = 0 name = "" } 33
  • 88.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } 34
  • 89.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized 34
  • 90.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree 34
  • 91.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize 34
  • 92.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize Difficult access 34
  • 93.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize Difficult access Denormalized 34
  • 94.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize Difficult access Denormalized Object graph 34
  • 95.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize Difficult access Denormalized Object graph Harder serialization 34
  • 96.
    Domain StateDomain State treeor graph?tree or graph? // domain todo model class Todo { name = ""; done = false; user_id = 0; // ... } // domain user model class User { id = 0 name = "" } // domain todo model class Todo { name = ""; done = false; user = new User(0, ""); // ... } // domain user model class User { id = 0 name = "" } Normalized Object tree Easily serialize Difficult access Denormalized Object graph Harder serialization Easy access 34
  • 97.
    Domain StateDomain State goodnews everyone!good news everyone! 35
  • 98.
    Domain StateDomain State letsdo both!lets do both! // domain todo model class Todo { name = "" done = false user_id = 0 get user(){ return store.getUserById(this.user_id) // <- WTF } set user(value){ this.user_id = value.id } } decorate(Todo, { //... user_id: observable, user: computed }) 36
  • 99.
    Domain StateDomain State letsdo both!lets do both! // domain todo model class Todo { name = "" done = false user_id = 0 get user(){ return store.getUserById(this.user_id) // <- WTF } set user(value){ this.user_id = value.id } } decorate(Todo, { //... user_id: observable, user: computed }) 36
  • 100.
    Multiple Store CommunicationMultipleStore Communication available pattersavailable patters 37
  • 101.
    Multiple Store CommunicationMultipleStore Communication available pattersavailable patters Singleton instance & require/import 37
  • 102.
    Multiple Store CommunicationMultipleStore Communication available pattersavailable patters Singleton instance & require/import Dependency injection framework 37
  • 103.
    Multiple Store CommunicationMultipleStore Communication available pattersavailable patters Singleton instance & require/import Dependency injection framework Root store pattern 37
  • 104.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern class RootStore { todoStore = null; fetch = null; apiKey = ""; constructor(fetch, apiKey){ this.fetch = fetch this.apiKey = apiKey this.todoStore = new Store(this) } } class Todo { store = null; constructor(store){ this.store = store } } class Store { rootStore = null; constructor(rootStore){ this.rootStore = rootStore } } 38
  • 105.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern 39
  • 106.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern Central point for each store to communicate 39
  • 107.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern Central point for each store to communicate Strongly typed 39
  • 108.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern Central point for each store to communicate Strongly typed Works as dependency root Can host environment specific variables 39
  • 109.
    Multiple Store CommunicationMultipleStore Communication root store patternroot store pattern Central point for each store to communicate Strongly typed Works as dependency root Can host environment specific variables Very easily testable 39
  • 110.
    Multiple Store CommunicationMultipleStore Communication back to our problemback to our problem // domain todo model class Todo { name = "" done = false user_id = 0 store = null; constructor(store){ this.store = store; } get user(){ return this.store.rootStore .userStore.getUserById(this.user_id) } set user(value){ this.user_id = value.id } } 40
  • 111.
    Multiple Store CommunicationMultipleStore Communication back to our problemback to our problem import { observer, Provider, inject } from "mobx-react"; // ... const MyApp = inject("store")(observer(App)); const store = new RootStore(window.fetch); const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <MyApp /> </Provider>, rootElement); 41
  • 112.
    Multiple Store CommunicationMultipleStore Communication back to our problemback to our problem test("it should restore data from API", async t => { const fakeFetch = () => Promise.resolve({ data: [{id: 1, name: "Mattia"}] }) const store = new RootStore(fakeFetch) await store.userStore.fetchAll() t.equal(store.userStore.users[0].name, "Mattia") }) 42
  • 113.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW STORE Renders the view and calls actions on the store Holds domain state Create and modify domain state Holds and modify application state Implements business logic Fetch from API Aggregates data for the view DOMAIN MODEL 43
  • 114.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW STORE Renders the view and calls actions on the store Holds domain state Create and modify domain state Holds and modify application state Implements business logic Fetch from API Aggregates data for the view DOMAIN MODEL 43
  • 115.
    Domain Model SerializationDomainModel Serialization turning JSON into observablesturning JSON into observables 44
  • 116.
    Domain Model SerializationDomainModel Serialization turning JSON into observablesturning JSON into observables Our state is an object tree 44
  • 117.
    Domain Model SerializationDomainModel Serialization turning JSON into observablesturning JSON into observables Our state is an object tree Unfortunately it's not a plain JS object 44
  • 118.
    Domain Model SerializationDomainModel Serialization turning JSON into observablesturning JSON into observables Our state is an object tree Unfortunately it's not a plain JS object Most APIs provide JSON as intermediate language 44
  • 119.
    Domain Model SerializationDomainModel Serialization turning JSON into observablesturning JSON into observables Our state is an object tree Unfortunately it's not a plain JS object Most APIs provide JSON as intermediate language We need to provide conversions to serialize or deserialize our state 44
  • 120.
    Domain Model SerializationDomainModel Serialization deserializationdeserialization class Todo { // ... constructor(store, data){ this.store = store if(data) Object.assign(this, data) } } 45
  • 121.
    Domain Model SerializationDomainModel Serialization serializationserialization class Todo { // ... get toJSON(){ return { name: this.name, done: this.done } } } // ... decorate(Todo, { toJSON: computed }) JSON is just another view of the domain model, so we can just derive itderive 46
  • 122.
    Domain Model SerializationDomainModel Serialization packagespackages class User { @serializable(identifier()) id = 0; @serializable name = ''; } class Todo { @serializable name = ''; @serializable done = false; @serializable(object(User)) user = null; } // You can now deserialize and serialize! const todo = deserialize(Todo, { name: 'Hello world', done: true, user: { id: 1, name: 'Mattia' } }); const todoJSON = serialize(todo) with serializr you get that with tagging your domain models 47
  • 123.
    Domain Model SerializationDomainModel Serialization the deserialization problemthe deserialization problem class TodoStore { todos = [] fromCache(){ const cachedData = localStorage.getItem("todos") || "[]" this.todos = JSON.parse(cachedData) .map(data => new Todo(this, data) } getById = id => this.todos .find(item => item.id === id) } decorate(TodoStore, { fromCache: action }) deserializing is memory intensive! 48
  • 124.
    MobX hidden featureMobXhidden feature _interceptReads_interceptReads import {_interceptReads} from "mobx" const todos = observable.map() _interceptReads(todos, value => value + "! LOL") todos.set(1, "Mattia") console.log(todos.get("1")) // => Mattia! LOL undocumented (yet there since 2017) mobx feature allows to transform mobx values while reading available for objects, arrays, maps and boxed values 49
  • 125.
    MobX hidden featureMobXhidden feature _interceptReads to the rescue!_interceptReads to the rescue! class TodoStore { todos = [] _cache = {} constructor(rootStore){ this.rootStore = rootStore // ... _interceptReads(this.todos, this.unboxTodo) } unboxTodo = data => { if(this._cache[data.id]){ return this._cache[data.id] } this._cache[data.id] = new Todo(this, data) return this._cache[data.id] } } 50
  • 126.
    MobX PerformanceMobX Performance betterperformant domain storesbetter performant domain stores 51
  • 127.
    MobX PerformanceMobX Performance betterperformant domain storesbetter performant domain stores Use ES6 Maps and lookup by ID when possible 51
  • 128.
    MobX PerformanceMobX Performance betterperformant domain storesbetter performant domain stores Use ES6 Maps and lookup by ID when possible Store JSON in an observable map 51
  • 129.
    MobX PerformanceMobX Performance betterperformant domain storesbetter performant domain stores Use ES6 Maps and lookup by ID when possible Store JSON in an observable map Use _interceptReads to perform lazy deserialization 51
  • 130.
    MobX PerformanceMobX Performance betterperformant domain storesbetter performant domain stores Use ES6 Maps and lookup by ID when possible Store JSON in an observable map Use _interceptReads to perform lazy deserialization Implement ID -> name without deserialization 51
  • 131.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW REPOSITORY Renders the view and calls actions on the store Holds domain state Create and modify domain state Fetch from API Holds and modify application state Implements business logic Aggregates data for the view STORE DOMAIN MODEL 52
  • 132.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW REPOSITORY Renders the view and calls actions on the store Holds domain state Create and modify domain state Fetch from API Holds and modify application state Implements business logic Aggregates data for the view STORE DOMAIN MODEL 52
  • 133.
    class Overview { rootStore= null currentText = "" constructor(rootStore){ this.rootStore = rootStore } get todos(){ // perform sorting and pagining here return this.rootStore.todoStore.todos } handleAddClicked(){ this.rootStore.todoStore.addTodo(this.currentText) this.currentText = "" } } decorate(Router, { todos: computed, currentText: observable, handleAddClicked: action }) 53
  • 134.
    Services & PresentersServices& Presenters routingrouting class Router { uri = "/" page = null // ... constructor(rootStore){ reaction(() => this.uri, uri => this.parseUri(uri)) } openOverview(){ this.page = { name: "overview", data: { project_id: 1 }} } } decorate(Router, { uri: observable, page: observable }) 54
  • 135.
    Services & PresentersServices& Presenters routingrouting class Router { uri = "/" page = null // ... constructor(rootStore){ reaction(() => this.uri, uri => this.parseUri(uri)) } openOverview(){ this.page = { name: "overview", data: { project_id: 1 }} } } decorate(Router, { uri: observable, page: observable }) Where load that data? Where load that data? 54
  • 136.
    Services & PresentersServices& Presenters view presenterview presenter class Router { uri = "/" page = null constructor(rootStore){ this.page = this.loadHome() reaction(() => this.uri, uri => this.parseUri(uri)) } openOverview(){ this.page = fromPromise( this.overviewStore.fetchData() .then(data => Promise.resolve({ name: "overview", data }) ) ) } } 55
  • 137.
    Services & PresentersServices& Presenters view presenterview presenter class Router { uri = "/" page = null constructor(rootStore){ this.page = this.loadHome() reaction(() => this.uri, uri => this.parseUri(uri)) } openOverview(){ this.page = fromPromise( this.overviewStore.fetchData() .then(data => Promise.resolve({ name: "overview", data }) ) ) } } 55
  • 138.
    Services & PresentersServices& Presenters fromPromisefromPromise class Router { uri = "/" page = null constructor(rootStore){ this.page = this.loadHome() reaction(() => this.uri, uri => this.parseUri(uri)) } openOverview(){ this.page = fromPromise( this.overviewStore.fetchData() .then(data => Promise.resolve({ name: "overview", data }) ) ) } } 56
  • 139.
    MobX Async TipsMobXAsync Tips reactive finite state machinesreactive finite state machines <div> { this.page.case({ pending: () => "loading", rejected: (e) => "error: " + e, fulfilled: ({name, data}) => { switch(name){ case "home": return <HomePage data={data} />; case "overview": return <Overview data={data} />; } } })} </div> 57
  • 140.
    Real-World ArchitectureReal-World Architecture CurrentState RecapCurrent State Recap VIEW REPOSITORY Renders the view and calls actions on the store Holds domain state Create and modify domain state Fetch from API Holds and modify application state Implements business logic SERVICE DOMAIN MODEL PRESENTER Aggregates data for the view Call actions on the services 58
  • 141.
  • 142.
    Today's RecapToday's Recap 1.Use Redux if you're paid by the hour 59
  • 143.
    Today's RecapToday's Recap 1.Use Redux if you're paid by the hour 2. Find your minimal state 59
  • 144.
    Today's RecapToday's Recap 1.Use Redux if you're paid by the hour 2. Find your minimal state 3. Derive whatever is possible 59
  • 145.
    Today's RecapToday's Recap 1.Use Redux if you're paid by the hour 2. Find your minimal state 3. Derive whatever is possible 4. Think as you're building for any UI environment 59
  • 146.
    Today's RecapToday's Recap 1.Use Redux if you're paid by the hour 2. Find your minimal state 3. Derive whatever is possible 4. Think as you're building for any UI environment 5. Always experiment and have fun! 59
  • 147.
    Want something opinionated?Wantsomething opinionated? 1. Define store shape 2. Automatic serialization/deserialization 3. Implements both a immutable & mutable store 4. Defines a stricter architecture 5. Time travelling discover mobx-state-treediscover mobx-state-tree 60
  • 148.
    Not into mutablethings?Not into mutable things? stay here for the next talk!stay here for the next talk! 61
  • 149.
    Thanks for yourtime!Thanks for your time! Questions?Questions?   @MattiaManzati - slides.com/mattiamanzati 62
  • 150.