Connecting components
Now that you have defined a state describing your application, you probably want to transform that state into a user interface. There are many ways to express this and Overmind supports the most popular libraries and frameworks for doing this transformation, typically called a view layer. You can also implement a custom view layer if you want to.
By installing the view layer of choice you will be able to connect it to your Overmind instance, exposing its state, actions and effects.
React
Angular
Vue
App.jsx
1
import * as React from 'react'
2
import { useAppState } from '../../overmind'
3
4
const App = () => {
5
const state = useAppState()
6
7
if (state.isLoading) {
8
return <div>Loading app...</div>
9
}
10
11
return <h1>My awesome app</h1>
12
}
13
14
export default App
Copied!
app.component.ts
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-root',
6
template: `
7
<div *track>
8
<div *ngIf="state.isLoading">
9
Loading app...
10
</div>
11
<h1 *ngIf="!state.isLoading">
12
My awesome app
13
</h1>
14
</div>
15
`
16
})
17
export class App {
18
state = this.store.select()
19
constructor(private store: Store) {}
20
}
Copied!
1
<template>
2
<div v-if="state.isLoading">
3
Loading app...
4
</div>
5
<h1 v-else>My awesome app</h1>
6
</template>
Copied!
In this example we are accessing the isLoading state. When this component renders and this state is accessed, Overmind will automatically understand that this component is interested in this exact state. It means that whenever the value is changed, this component will render again.

State

When Overmind detects that the App component is interested in our isLoading state, it is not looking at the value itself, it is looking at the path. The component is pointed to state.isLoading which means that when a mutation occurs on that path in the state, the component will render again. Since the value is a boolean value this can only happen when isLoading is replaced or removed. The same goes for strings and numbers as well. We do not say that we mutate a string, boolean or a number. We mutate the object or array that holds those values.
The story is a bit different if the state value is an object or an array. These values can not only be replaced and removed, they can also mutate themselves. An object can have keys added or removed. An array can have items added, removed and even change the order of items. Overmind knows this and will notify components respectively. Let us look at how Overmind treats the following scenarios to get a better understanding.

Arrays

When we just access an array in a component it will re-render if the array itself is replaced, removed or we do a mutation to it. That would mean we push a new item to it, we splice it or sort it.
React
Angular
Vue
1
import * as React from 'react'
2
import { useAppState } from '../overmind'
3
4
const List = () => {
5
const state = useAppState()
6
7
return (
8
<h1>{state.items}</h1>
9
)
10
}
11
12
export default List
Copied!
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-list',
6
template: `
7
<h1 *track>{{state.items}}</h1>
8
`
9
})
10
export class List {
11
state = this.store.select()
12
constructor(private store: Store) {}
13
}
Copied!
1
<template>
2
<h1>{{ state.items }}</h1>
3
</template>
Copied!
But what happens if we iterate the array and access a property on each item?
React
Angular
Vue
1
import * as React from 'react'
2
import { useAppState } from '../overmind'
3
4
const List = () => {
5
const state = useAppState()
6
7
return (
8
<ul>
9
{state.items.map(item =>
10
<li key={item.id}>{item.title}</li>
11
)}
12
</ul>
13
)
14
}
15
16
export default App
Copied!
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-list',
6
template: `
7
<ul>
8
<li *ngFor="let item of state.items;trackby: trackById">
9
{{item.title}}
10
</li>
11
</ul>
12
`
13
})
14
export class List {
15
state = this.store.select()
16
constructor(private store: Store) {}
17
trackById(index, item) {
18
return item.id
19
}
20
}
Copied!
1
<template>
2
<ul>
3
<li v-for="item in state.items" :key="item.id">
4
{{ item.title }}
5
</li>
6
</ul>
7
</template>
Copied!
Now the List component looks at the list itself and all the items. Meaning if any items are added/removed or any of the items change, the List component will render again. Read more about Managing lists to understand better how to optimize this.

Objects

Objects are similar to arrays. If you access an object you track if that object is replaced or removed. As with arrays, you can mutate the object itself. When you add, replace or remove a key from the object, it is considered a mutation of the object. It means that if you just access the object, the component will render if any keys are added, replaced or removed.
React
Angular
Vue
1
import * as React from 'react'
2
import { useAppState } from '../overmind'
3
4
const List = () => {
5
const state = useAppState()
6
7
return (
8
<h1>{state.items}</h1>
9
)
10
}
11
12
export default List
Copied!
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-list',
6
template: `
7
<h1>{{state.items}}</h1>
8
`
9
})
10
export class List {
11
state = this.store.select()
12
constructor(private store: Store) {}
13
}
Copied!
1
<template>
2
<h1>{{ state.items }}</h1>
3
</template>
Copied!
And just like an array you can iterate the values of en object, which results in the component rendering again when any object key value changes:
React
Angular
Vue
List.jsx
1
import * as React from 'react'
2
import { useAppState } from '../overmind'
3
4
const List = () => {
5
const state = useAppState()
6
7
return (
8
<ul>
9
{Object.keys(state.items).map(key =>
10
<li key={key}>{state.items[key].title}</li>
11
)}
12
</ul>
13
)
14
}
15
16
export default List
Copied!
list.component.ts
1
import { Component Input } from '@angular/core';
2
import { Item } from '../overmind/state'
3
4
@Component({
5
selector: 'app-list-item',
6
template: `
7
<li *track>
8
{{item.title}}
9
</li>
10
`
11
})
12
export class List {
13
@Input() item: Item;
14
}
Copied!
List.vue
1
<template>
2
<ul>
3
<li is="Item" v-for="item in state.items" :item="item" :key="item.id" />
4
</ul>
5
</template>
6
<script>
7
import Item from './Item'
8
9
export default {
10
name: 'List',
11
components: {
12
Item,
13
},
14
}
15
</script>
Copied!
Again look at Managing lists to understand better how to optimize lists.

Actions

All the actions defined in the Overmind application are available to connected components.
overmind/actions.js
1
export const toggleAwesomeApp = ({ state }) => {
2
state.isAwesome = !state.isAwesome
3
}
Copied!
React
Angular
Vue
1
import * as React from 'react'
2
import { useActions } from '../overmind'
3
4
const App = () => {
5
const actions = useActions()
6
7
return (
8
<button onClick={actions.toggleAwesomeApp}>
9
Toggle awesome
10
</button>
11
)
12
}
13
14
export default App
Copied!
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-root',
6
template: `
7
<button (click)="actions.toggleAwesomeApp()">
8
Toggle awesome
9
</button>
10
`
11
})
12
export class App {
13
actions = this.store.actions
14
constructor(private store: Store) {}
15
}
Copied!
1
<template>
2
<button @click="actions.toggleAwesomeApp()">
3
Toggle awesome
4
</button>
5
</template>
Copied!
If you need to pass multiple values to an action, you should rather use an object instead.

Reactions

Sometimes you want to make something happen inside a component related to a state change. This is typically doing some manual work on the DOM. When you connect a component to Overmind it also gets access to reaction. This function allows you to subscribe to changes in state, mutations as we call them.
This example shows how you can scroll to the top of the page every time you change the current article of the app.
React
Angular
Vue
1
import * as React from 'react'
2
import { useReaction } from '../../overmind'
3
4
const Article = () => {
5
const reaction = useReaction()
6
7
React.useEffect(() => reaction(
8
(state) => state.currentArticle,
9
() => {
10
document.querySelector('#app').scrollTop = 0
11
}
12
), [])
13
14
return <article />
15
}
16
17
export default Article
Copied!
1
import { Component } from '@angular/core';
2
import { Store } from '../overmind'
3
4
@Component({
5
selector: 'app-root',
6
template: `
7
<article></article>
8
`
9
})
10
export class App {
11
disposeEffect: () => void
12
constructor(private store: Store) {}
13
ngOnInit() {
14
this.disposeReaction = this.store.reaction(
15
(state) => state.currentArticle,
16
() => document.querySelector('#app').scrollTop = 0
17
)
18
}
19
ngOnDestroy() {
20
this.disposeReaction()
21
}
22
}
Copied!
1
<template>
2
<article></article>
3
</template>
4
<script>
5
export default {
6
name: 'Article',
7
mounted() {
8
this.disposeReaction = this.overmind.reaction(
9
(state) => state.currentArticle,
10
() => document.querySelector('#app').scrollTop = 0
11
})
12
}
13
destroyed() {
14
this.disposeReaction()
15
}
16
}
17
</script>
Copied!

Effects

Any effects you define in your Overmind application are also exposed to the components. They can be found on the property effects. It is encouraged that you keep your logic inside actions, but you might be in a situation where you want some other relationship between components and Overmind. A shared effect is the way to go.
Last modified 9mo ago