SlideShare a Scribd company logo
Introducing Vuex in your projects
How to add and use Vuex in existing projects, with an eye for testing.
 
- Denny Biasiolli -
WHO AM I
Denny Biasiolli
Freelance Full Stack Developer
Front End Developer UX/ UI
Fingerprint Supervision Ltd
Savigliano (CN) - Italy
Volunteer in a retirement home,
performing recreational activities
@dennybiasiolli
denny.biasiolli@gmail.com
dennybiasiolli.com
EXAMPLE APP
EXAMPLE APP
Structure
EXAMPLE APP
Main component, data()
export default {
name: 'Home',
data() { // component's state
return {
availableNumbers: [...Array(90).keys()]
.map((i) => i + 1),
extractedNumbers: [],
};
},
// ...
};
EXAMPLE APP
Main component, computed
export default {
// ...
computed: { // component's getters
ascendingExtractedNumbers() {
return [...this.extractedNumbers].sort((a, b) => a - b);
},
},
// ...
};
EXAMPLE APP
Main component, methods
export default {
// ...
methods: { // component's actions/mutations
handleExtract() {
const index = Math.floor(
Math.random() * this.availableNumbers.length);
const extracted = this.availableNumbers
.splice(index, 1);
this.extractedNumbers = this.extractedNumbers
.concat(extracted);
},
},
};
EXAMPLE APP
Main component template
<button @click="handleExtract">Extract</button>
<h1>
Extracted:
{{ extractedNumbers[extractedNumbers.length - 1] }}
</h1>
<DisplayNumbers title="Available numbers"
:numbers="availableNumbers" />
<DisplayNumbers title="Extracted numbers"
:numbers="ascendingExtractedNumbers" />
EXAMPLE APP
DisplayNumbers component
<v-card elevation="2">
<v-card-title>{{ title }}</v-card-title>
<v-card-text>
<v-chip v-for="n of numbers" :key="n" class="ma-1">
{{ n }}
</v-chip>
</v-card-text>
</v-card>
export default {
name: 'DisplayNumbers',
props: {
title: String,
numbers: Array,
},
};
COMPONENT TESTS
DisplayNumbers
import { shallowMount } from '@vue/test-utils';
import DisplayNumbers from '@/components/DisplayNumbers.vue';
test('renders as expected', () => {
const wrapper = shallowMount(DisplayNumbers, {
stubs: ['v-container', 'v-card', 'v-card-title', 'v-card-t
propsData: {
title: 'title text',
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
},
});
expect(wrapper).toMatchSnapshot();
});
COMPONENT TESTS
Home #1
import { shallowMount } from '@vue/test-utils';
import Home from '@/views/Home.vue';
const shallowMountComponent = () => shallowMount(Home, {
stubs: ['v-container', 'v-btn', 'v-row', 'v-col'],
});
test('renders as expected', () => {
const wrapper = shallowMountComponent();
expect(wrapper).toMatchSnapshot();
});
// ...
COMPONENT TESTS
Home #2
// ...
test('extracts a number and render as expected', async () => {
jest.spyOn(global.Math, 'random')
.mockReturnValueOnce(0.123456789)
.mockReturnValueOnce(0.987654321);
const wrapper = shallowMountComponent();
wrapper.vm.handleExtract();
await wrapper.vm.$nextTick();
expect(wrapper).toMatchSnapshot();
wrapper.vm.handleExtract();
await wrapper.vm.$nextTick();
expect(wrapper).toMatchSnapshot();
jest.spyOn(global.Math, 'random').mockRestore();
});
ONE-WAY DATA FLOW
STATE FLOW SUMMARY
Flow process Vue.js component
State data and computed
View <template>
Actions methods
STATE PROBLEM
Solution 1: Moving state to parent components
move data() from Home to App
receiving numbers in Home and Footer as props
emitting an event when "Extract" button is clicked
in Home
handling extract event in App component, moving
methods from Home to App
updating tests
PROS
fast and easy in small apps
keep the state in the components where it is used
(if there is no need to pass it to other components)
no extra dependencies
testing sub-components with propsData and
snapshots
CONS
multiple views may depend on the same piece of
state
actions from different views may need to mutate the
same piece of state
messy on big apps, lots of extra code for passing
props, emitting events
hard to follow state changes on many levels
what is causing a data change?
WHAT IS VUEX?
A state management pattern/library
for Vue.js applications.
It serves as a centralized store for all
the components in an application,
with rules ensuring that the state can
only be mutated in a predictable
fashion.
https://vuex.vuejs.org/
WHEN SHOULD I USE IT?
There's a good quote from Dan Abramov,
the author of Redux:
Flux libraries are like glasses: you’ll
know when you need them.
https://vuex.vuejs.org/
WHEN SHOULD I USE IT?
It's a trade-off between short term and long term productivity.
If you jump right into Vuex, it may feel verbose and daunting.
But if you are building a medium-to-large-scale SPA, chances are you have run into
situations that make you think about how to better handle state outside of your Vue
components, and Vuex will be the natural next step for you.
https://vuex.vuejs.org/
VUEX FLOW
INSTALL VUEX
or
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
npm install --save vuex
# or
yarn add vuex # https://yarnpkg.com/
# or
npx @vue/cli add vuex # https://cli.vuejs.org/
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
https://vuex.vuejs.org/installation.html
CONFIGURE VUEX
Creating the store
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: { /* ... */ },
mutations: { /* ... */ },
});
https://vuex.vuejs.org/guide/
CONFIGURE VUEX
Enabling this.$store inside Vue components
// src/main.js
// ...
import store from './store';
new Vue({
store, // same as `store: store`
// ...
});
https://vuex.vuejs.org/guide/
CONCEPTS: STATE
Creation
new Vuex.Store({
state: {
count: 0
},
// ...
});
https://vuex.vuejs.org/guide/state.html
CONCEPTS: STATE
Basic usage
<div>
{{ $store.state.count }}
{{ count }}
</div>
computed: {
count () {
return this.$store.state.count;
}
}
https://vuex.vuejs.org/guide/state.html
CONCEPTS: STATE
mapState usage
import { mapState } from 'vuex';
export default {
// ...
computed: mapState({
count: state => state.count,
countAlias: 'count',
// to access local state with `this`
countPlusLocalState (state) {
return state.count + this.localCount;
}
})
};
https://vuex.vuejs.org/guide/state.html
CONCEPTS: STATE
mapState usage simplified
is the same as
mapState({
count: state => state.count
})
mapState([
'count'
])
https://vuex.vuejs.org/guide/state.html
CONCEPTS: STATE
mapState usage with other computed values
computed: {
...mapState({
// ...
}),
localComputed () { /* ... */ }
}
https://vuex.vuejs.org/guide/state.html
CONCEPTS: GETTERS
Getters are like "computed" values for a Vuex store
Creation
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
countIsEven: state => {
return state.count % 2 === 0;
}
}
});
https://vuex.vuejs.org/guide/getters.html
CONCEPTS: GETTERS
Basic usage
<div>
{{ $store.getters.countIsEven }}
{{ countIsEven }}
</div>
computed: {
countIsEven () {
return this.$store.getters.countIsEven;
}
}
https://vuex.vuejs.org/guide/getters.html
CONCEPTS: GETTERS
mapGetters usage
import { mapGetters } from 'vuex';
export default {
// ...
computed: mapGetters({
countIsEvenAlias: 'countIsEven'
})
};
https://vuex.vuejs.org/guide/getters.html
CONCEPTS: GETTERS
mapGetters advanced usage
computed: {
...mapState(['count']),
...mapGetters(['countIsEven']),
localComputed () { /* ... */ }
}
https://vuex.vuejs.org/guide/getters.html
CONCEPTS: MUTATIONS
Committing a mutation is the only way to actually
change state in a Vuex store.
Creation
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state, payload=1) {
state.count += payload;
}
}
});
https://vuex.vuejs.org/guide/mutations.html
CONCEPTS: MUTATIONS
Basic usage
methods: {
increment (value) {
return this.$store.commit('increment', value);
}
}
https://vuex.vuejs.org/guide/mutations.html
CONCEPTS: MUTATIONS
mapMutations usage
import { mapMutations } from 'vuex';
export default {
// ...
methods: {
...mapMutations([
'increment'
]),
...mapMutations({
add: 'increment'
})
}
};
https://vuex.vuejs.org/guide/mutations.html
MUTATIONS MUST BE SYNCHRONOUS
Why? Because we need to have a "before" and "a er"
snapshots of the state.
If we introduce a callback inside a mutation, it makes
that impossible.
The callback is not called yet when the mutation is
committed, and there's no way to know when the
callback will actually be called. Any state mutation
performed in the callback is essentially un-trackable!
https://vuex.vuejs.org/guide/mutations.html
CONCEPTS: ACTIONS
Actions are similar to mutations, with a few
differences:
Instead of mutating the state, actions commit
mutations.
Actions can contain arbitrary asynchronous
operations.
https://vuex.vuejs.org/guide/actions.html
CONCEPTS: ACTIONS
Creation
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment (state, payload=1) {
state.count += payload;
}
},
actions: {
incrementAsync (context, payload) {
setTimeout(() => {
context.commit('increment', payload);
}, 1000);
}
}
});
https://vuex.vuejs.org/guide/actions.html
CONCEPTS: ACTIONS
API call example
const store = new Vuex.Store({
actions: {
async getRecords (context) {
context.commit('getRecordsRequest');
try {
const results = await axios.get('/api/records/');
context.commit('getRecordsSuccess', results.data);
} catch (error) {
context.commit('getRecordsFailure', error);
}
}
}
});
https://vuex.vuejs.org/guide/actions.html
CONCEPTS: ACTIONS
Context object
context.commit to commit a mutation
context.state access the state
context.getters access the getters
context.dispatch to call other actions
https://vuex.vuejs.org/guide/actions.html
CONCEPTS: ACTIONS
mapActions usage
import { mapActions } from 'vuex';
export default {
// ...
methods: {
incrementAsyncLocal (value) {
return this.$store.dispatch('incrementAsync', value)
.then( /* ... */);
}
...mapActions(['incrementAsync']),
...mapActions({
addAsync: 'incrementAsync'
})
}
};
https://vuex.vuejs.org/guide/actions.html
EXAMPLE APP
Creating the store, default state
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const defaultState = {
availableNumbers: [...Array(90).keys()]
.map((i) => i + 1),
extractedNumbers: [],
};
EXAMPLE APP
Creating the store, getters
// src/store/index.js
export const getters = {
ascendingExtractedNumbers(state) {
return [...state.extractedNumbers].sort((a, b) => a - b);
},
};
EXAMPLE APP
Creating the store, mutations
// src/store/index.js
export const mutations = {
extractNumber(state) {
const index = Math.floor(
Math.random() * state.availableNumbers.length);
const extracted = state.availableNumbers
.splice(index, 1);
state.extractedNumbers = state.extractedNumbers
.concat(extracted);
},
};
EXAMPLE APP
Creating the store, composing
// src/store/index.js
export default new Vuex.Store({
state: defaultState,
getters,
mutations,
});
EXAMPLE APP
Home component, data and computed function
@@ src/views/Home.vue
- data() {
- return {
- availableNumbers: [...Array(90).keys()].map((i) => i +
- extractedNumbers: [],
- };
- },
computed: {
- ascendingExtractedNumbers() {
- return [...this.extractedNumbers].sort((a, b) => a - b)
- },
+ ...mapState(['availableNumbers', 'extractedNumbers']),
+ ...mapGetters(['ascendingExtractedNumbers']),
},
EXAMPLE APP
Home component, method
@@ src/views/Home.vue
- <button @click="handleExtract">Extract</button>
+ <button @click="extractNumber">Extract</button>
methods: {
- handleExtract() {
- const index = Math.floor(Math.random() * this.available
- const extracted = this.availableNumbers.splice(index, 1
- this.extractedNumbers = this.extractedNumbers.concat(ex
- },
+ ...mapMutations(['extractNumber']),
},
MOVING TO VUEX STORE FLOW
Vue.js component Vuex store Map in
data state computed
computed getters computed
sync methods mutations methods
async methods actions methods
STORE TESTS
Default state
import { defaultState } from '@/store';
test('should have the default state', () => {
expect(defaultState).toEqual({
availableNumbers: [...Array(90).keys()].map((i) => i + 1),
extractedNumbers: [],
});
});
STORE TESTS
Getters
import { getters } from '@/store';
const { ascendingExtractedNumbers } = getters;
test('ascendingExtractedNumbers', () => {
expect(
ascendingExtractedNumbers({
extractedNumbers: [12, 56, 34]
})
).toEqual([12, 34, 56]);
});
STORE TESTS
Mutations
import { mutations } from '@/store';
const { defaultState, extractNumber } = mutations;
test('ascendingExtractedNumbers', () => {
jest.spyOn(global.Math, 'random')
.mockReturnValueOnce(0.123456789)
.mockReturnValueOnce(0.987654321);
const state = { ...defaultState };
expect(state.availableNumbers).toHaveLength(90);
// ...
STORE TESTS
Mutations
// ...
extractNumber(state);
expect(state.availableNumbers).toHaveLength(89);
expect(state.extractedNumbers).toEqual([12]);
extractNumber(state);
expect(state.availableNumbers).toHaveLength(88);
expect(state.extractedNumbers).toEqual([12, 89]);
jest.spyOn(global.Math, 'random').mockRestore();
});
STORE TESTS
Actions
Keep in mind this sample action
// export const actions = {
async getRecords (context) {
context.commit('getRecordsRequest');
try {
const results = await axios.get('/api/records/');
context.commit('getRecordsSuccess', results.data);
} catch (error) {
context.commit('getRecordsFailure', error);
}
}
// };
STORE TESTS
Actions
Mocking calls using jest
.mockReturnValue(value) for mocking sync results
.mockResolvedValue(value) for mocking async results with success
.mockRejectedValue(value) for mocking async results with failure
import axios from 'axios';
import { actions } from '@/store';
jest.mock('axios', () => ({
get: jest.fn(),
}));
https://jestjs.io/docs/mock-functions
STORE TESTS
Actions
Mocking axios success
const { getRecords } = actions;
test('getRecords success', async () => {
const commit = jest.fn();
axios.get.mockResolvedValue({ data: 'ok' });
await getRecords({ commit });
expect(commit).toHaveBeenCalledWith('getRecordsRequest');
expect(axios.get).toHaveBeenCalledWith('/api/records/');
expect(commit).toHaveBeenCalledWith(
'getRecordsSuccess', 'ok');
});
STORE TESTS
Actions
Mocking axios failures
const { getRecords } = actions;
test('getRecords failure', async () => {
const commit = jest.fn();
axios.get.mockRejectedValue('my error');
try {
await getRecords({ commit });
// Fail test if above expression doesn't throw anything
expect(true).toBe(false);
} catch (error) {
expect(commit).toHaveBeenCalledWith('getRecordsRequest');
expect(axios.get).toHaveBeenCalledWith('/api/records/');
expect(commit).toHaveBeenCalledWith(
'getRecordsFailure', 'my error');
}
});
COMPONENT TESTS USING ORIGINAL STORE
(NOT SUGGESTED)
import { shallowMount } from '@vue/test-utils';
import Home from '@/views/Home.vue';
import store from '@/store';
test('snapshot test with default props', () => {
const wrapper = shallowMount(Home, { store });
expect(wrapper).toMatchSnapshot();
});
https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
COMPONENT TESTS USING ORIGINAL STORE
(NOT SUGGESTED)
Pros
fast and easy, store implementation ready-to-use
Cons
less control over store mocking and external calls
https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
COMPONENT TESTS MOCKING THE STORE
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex';
import Home from '@/views/Home.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
// ...
https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
COMPONENT TESTS MOCKING THE STORE
// ...
describe('Home.vue', () => {
let state, getters, mutations, actions, store;
beforeEach(() => {
state = { count: 0 };
getters = { getter1: () => 'mocked return value' };
mutations = { mutation1: jest.fn() };
actions = { action1: jest.fn() };
store = new Vuex.Store({
state, getters, mutations, actions });
});
// ...
https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
COMPONENT TESTS MOCKING THE STORE
// ...
test('snapshot test with default props', () => {
const wrapper = shallowMount(Home, { store, localVue });
expect(wrapper).toMatchSnapshot();
// ...
});
});
https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
RECAP
What we learned today?
Why use Vuex
Install and configure a Vuex store
Test store and components
LINKS
@dennybiasiolli
vuex.vuejs.org
vue-test-utils.vuejs.org
github.com/dennybiasiolli/bingo-extraction
dennybiasiolli.com
Ad

More Related Content

What's hot (20)

React lecture
React lectureReact lecture
React lecture
Christoffer Noring
 
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Codemotion
 
Google Fit, Android Wear & Xamarin
Google Fit, Android Wear & XamarinGoogle Fit, Android Wear & Xamarin
Google Fit, Android Wear & Xamarin
Peter Friese
 
Reduxing like a pro
Reduxing like a proReduxing like a pro
Reduxing like a pro
Boris Dinkevich
 
Introductionandgreetings
IntroductionandgreetingsIntroductionandgreetings
Introductionandgreetings
Pozz ZaRat
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
Boris Dinkevich
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
Robert DeLuca
 
Ditching JQuery
Ditching JQueryDitching JQuery
Ditching JQuery
howlowck
 
JavaFX Pitfalls
JavaFX PitfallsJavaFX Pitfalls
JavaFX Pitfalls
Alexander Casall
 
Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
Christoffer Noring
 
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Katy Slemon
 
Google Play Services Rock
Google Play Services RockGoogle Play Services Rock
Google Play Services Rock
Peter Friese
 
Owl: The New Odoo UI Framework
Owl: The New Odoo UI FrameworkOwl: The New Odoo UI Framework
Owl: The New Odoo UI Framework
Odoo
 
Developing New Widgets for your Views in Owl
Developing New Widgets for your Views in OwlDeveloping New Widgets for your Views in Owl
Developing New Widgets for your Views in Owl
Odoo
 
Nativescript angular
Nativescript angularNativescript angular
Nativescript angular
Christoffer Noring
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
Ignacio Martín
 
Boosting Angular runtime performance
Boosting Angular runtime performanceBoosting Angular runtime performance
Boosting Angular runtime performance
Nir Kaufman
 
Evan Schultz - Angular Camp - ng2-redux
Evan Schultz - Angular Camp - ng2-reduxEvan Schultz - Angular Camp - ng2-redux
Evan Schultz - Angular Camp - ng2-redux
Evan Schultz
 
Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018
Nir Kaufman
 
How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!
Nir Kaufman
 
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Codemotion
 
Google Fit, Android Wear & Xamarin
Google Fit, Android Wear & XamarinGoogle Fit, Android Wear & Xamarin
Google Fit, Android Wear & Xamarin
Peter Friese
 
Introductionandgreetings
IntroductionandgreetingsIntroductionandgreetings
Introductionandgreetings
Pozz ZaRat
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
Robert DeLuca
 
Ditching JQuery
Ditching JQueryDitching JQuery
Ditching JQuery
howlowck
 
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Katy Slemon
 
Google Play Services Rock
Google Play Services RockGoogle Play Services Rock
Google Play Services Rock
Peter Friese
 
Owl: The New Odoo UI Framework
Owl: The New Odoo UI FrameworkOwl: The New Odoo UI Framework
Owl: The New Odoo UI Framework
Odoo
 
Developing New Widgets for your Views in Owl
Developing New Widgets for your Views in OwlDeveloping New Widgets for your Views in Owl
Developing New Widgets for your Views in Owl
Odoo
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
Ignacio Martín
 
Boosting Angular runtime performance
Boosting Angular runtime performanceBoosting Angular runtime performance
Boosting Angular runtime performance
Nir Kaufman
 
Evan Schultz - Angular Camp - ng2-redux
Evan Schultz - Angular Camp - ng2-reduxEvan Schultz - Angular Camp - ng2-redux
Evan Schultz - Angular Camp - ng2-redux
Evan Schultz
 
Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018Redux pattens - JSHeroes 2018
Redux pattens - JSHeroes 2018
Nir Kaufman
 
How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!How Angular2 Can Improve Your AngularJS Apps Today!
How Angular2 Can Improve Your AngularJS Apps Today!
Nir Kaufman
 

Similar to Introducing Vuex in your project (20)

Da Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazioneDa Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazione
Commit University
 
Vuex to Pinia, how to migrate an existing app
Vuex to Pinia, how to migrate an existing appVuex to Pinia, how to migrate an existing app
Vuex to Pinia, how to migrate an existing app
Denny Biasiolli
 
React redux
React reduxReact redux
React redux
Michel Perez
 
Vue js and Dyploma
Vue js and DyplomaVue js and Dyploma
Vue js and Dyploma
Yoram Kornatzky
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
Denny Biasiolli
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
Evangelia Mitsopoulou
 
Reactive.architecture.with.Angular
Reactive.architecture.with.AngularReactive.architecture.with.Angular
Reactive.architecture.with.Angular
Evan Schultz
 
Vuex
VuexVuex
Vuex
Asaquzzaman Mishu
 
A Blog About Using State in Next.js: Valuable Insights
A Blog About Using State in Next.js: Valuable InsightsA Blog About Using State in Next.js: Valuable Insights
A Blog About Using State in Next.js: Valuable Insights
Shiv Technolabs Pvt. Ltd.
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
Denny Biasiolli
 
How to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescriptHow to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescript
Katy Slemon
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
Clickky
 
How to implement multiple layouts using React router V4.pptx
How to implement multiple layouts using React router V4.pptxHow to implement multiple layouts using React router V4.pptx
How to implement multiple layouts using React router V4.pptx
BOSC Tech Labs
 
React for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence ConnectReact for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence Connect
Atlassian
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0
Takuya Tejima
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
Astrails
 
Love at first Vue
Love at first VueLove at first Vue
Love at first Vue
Dalibor Gogic
 
Apollo. The client we deserve
Apollo. The client we deserveApollo. The client we deserve
Apollo. The client we deserve
Yuri Nezdemkovski
 
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Luciano Mammino
 
[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법
Jeado Ko
 
Da Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazioneDa Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazione
Commit University
 
Vuex to Pinia, how to migrate an existing app
Vuex to Pinia, how to migrate an existing appVuex to Pinia, how to migrate an existing app
Vuex to Pinia, how to migrate an existing app
Denny Biasiolli
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
Denny Biasiolli
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
Evangelia Mitsopoulou
 
Reactive.architecture.with.Angular
Reactive.architecture.with.AngularReactive.architecture.with.Angular
Reactive.architecture.with.Angular
Evan Schultz
 
A Blog About Using State in Next.js: Valuable Insights
A Blog About Using State in Next.js: Valuable InsightsA Blog About Using State in Next.js: Valuable Insights
A Blog About Using State in Next.js: Valuable Insights
Shiv Technolabs Pvt. Ltd.
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
Denny Biasiolli
 
How to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescriptHow to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescript
Katy Slemon
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
Clickky
 
How to implement multiple layouts using React router V4.pptx
How to implement multiple layouts using React router V4.pptxHow to implement multiple layouts using React router V4.pptx
How to implement multiple layouts using React router V4.pptx
BOSC Tech Labs
 
React for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence ConnectReact for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence Connect
Atlassian
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0
Takuya Tejima
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
Astrails
 
Apollo. The client we deserve
Apollo. The client we deserveApollo. The client we deserve
Apollo. The client we deserve
Yuri Nezdemkovski
 
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Luciano Mammino
 
[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법
Jeado Ko
 
Ad

Recently uploaded (20)

Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
TrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business ConsultingTrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business Consulting
Trs Labs
 
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
BookNet Canada
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptxSpecial Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
shyamraj55
 
Rusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond SparkRusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond Spark
carlyakerly1
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?
Daniel Lehner
 
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded DevelopersLinux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Toradex
 
What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...
Vishnu Singh Chundawat
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
Drupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy ConsumptionDrupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy Consumption
Exove
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
SOFTTECHHUB
 
Technology Trends in 2025: AI and Big Data Analytics
Technology Trends in 2025: AI and Big Data AnalyticsTechnology Trends in 2025: AI and Big Data Analytics
Technology Trends in 2025: AI and Big Data Analytics
InData Labs
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdfThe Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
Abi john
 
Role of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered ManufacturingRole of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered Manufacturing
Andrew Leo
 
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
TrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business ConsultingTrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business Consulting
Trs Labs
 
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
#StandardsGoals for 2025: Standards & certification roundup - Tech Forum 2025
BookNet Canada
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptxSpecial Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
shyamraj55
 
Rusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond SparkRusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond Spark
carlyakerly1
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?
Daniel Lehner
 
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded DevelopersLinux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Toradex
 
What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...
Vishnu Singh Chundawat
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
Drupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy ConsumptionDrupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy Consumption
Exove
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
AI EngineHost Review: Revolutionary USA Datacenter-Based Hosting with NVIDIA ...
SOFTTECHHUB
 
Technology Trends in 2025: AI and Big Data Analytics
Technology Trends in 2025: AI and Big Data AnalyticsTechnology Trends in 2025: AI and Big Data Analytics
Technology Trends in 2025: AI and Big Data Analytics
InData Labs
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdfThe Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
The Evolution of Meme Coins A New Era for Digital Currency ppt.pdf
Abi john
 
Role of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered ManufacturingRole of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered Manufacturing
Andrew Leo
 
Ad

Introducing Vuex in your project

  • 1. Introducing Vuex in your projects How to add and use Vuex in existing projects, with an eye for testing.   - Denny Biasiolli -
  • 2. WHO AM I Denny Biasiolli Freelance Full Stack Developer Front End Developer UX/ UI Fingerprint Supervision Ltd Savigliano (CN) - Italy Volunteer in a retirement home, performing recreational activities @dennybiasiolli [email protected] dennybiasiolli.com
  • 5. EXAMPLE APP Main component, data() export default { name: 'Home', data() { // component's state return { availableNumbers: [...Array(90).keys()] .map((i) => i + 1), extractedNumbers: [], }; }, // ... };
  • 6. EXAMPLE APP Main component, computed export default { // ... computed: { // component's getters ascendingExtractedNumbers() { return [...this.extractedNumbers].sort((a, b) => a - b); }, }, // ... };
  • 7. EXAMPLE APP Main component, methods export default { // ... methods: { // component's actions/mutations handleExtract() { const index = Math.floor( Math.random() * this.availableNumbers.length); const extracted = this.availableNumbers .splice(index, 1); this.extractedNumbers = this.extractedNumbers .concat(extracted); }, }, };
  • 8. EXAMPLE APP Main component template <button @click="handleExtract">Extract</button> <h1> Extracted: {{ extractedNumbers[extractedNumbers.length - 1] }} </h1> <DisplayNumbers title="Available numbers" :numbers="availableNumbers" /> <DisplayNumbers title="Extracted numbers" :numbers="ascendingExtractedNumbers" />
  • 9. EXAMPLE APP DisplayNumbers component <v-card elevation="2"> <v-card-title>{{ title }}</v-card-title> <v-card-text> <v-chip v-for="n of numbers" :key="n" class="ma-1"> {{ n }} </v-chip> </v-card-text> </v-card> export default { name: 'DisplayNumbers', props: { title: String, numbers: Array, }, };
  • 10. COMPONENT TESTS DisplayNumbers import { shallowMount } from '@vue/test-utils'; import DisplayNumbers from '@/components/DisplayNumbers.vue'; test('renders as expected', () => { const wrapper = shallowMount(DisplayNumbers, { stubs: ['v-container', 'v-card', 'v-card-title', 'v-card-t propsData: { title: 'title text', numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], }, }); expect(wrapper).toMatchSnapshot(); });
  • 11. COMPONENT TESTS Home #1 import { shallowMount } from '@vue/test-utils'; import Home from '@/views/Home.vue'; const shallowMountComponent = () => shallowMount(Home, { stubs: ['v-container', 'v-btn', 'v-row', 'v-col'], }); test('renders as expected', () => { const wrapper = shallowMountComponent(); expect(wrapper).toMatchSnapshot(); }); // ...
  • 12. COMPONENT TESTS Home #2 // ... test('extracts a number and render as expected', async () => { jest.spyOn(global.Math, 'random') .mockReturnValueOnce(0.123456789) .mockReturnValueOnce(0.987654321); const wrapper = shallowMountComponent(); wrapper.vm.handleExtract(); await wrapper.vm.$nextTick(); expect(wrapper).toMatchSnapshot(); wrapper.vm.handleExtract(); await wrapper.vm.$nextTick(); expect(wrapper).toMatchSnapshot(); jest.spyOn(global.Math, 'random').mockRestore(); });
  • 14. STATE FLOW SUMMARY Flow process Vue.js component State data and computed View <template> Actions methods
  • 16. Solution 1: Moving state to parent components move data() from Home to App receiving numbers in Home and Footer as props emitting an event when "Extract" button is clicked in Home handling extract event in App component, moving methods from Home to App updating tests
  • 17. PROS fast and easy in small apps keep the state in the components where it is used (if there is no need to pass it to other components) no extra dependencies testing sub-components with propsData and snapshots
  • 18. CONS multiple views may depend on the same piece of state actions from different views may need to mutate the same piece of state messy on big apps, lots of extra code for passing props, emitting events hard to follow state changes on many levels what is causing a data change?
  • 19. WHAT IS VUEX? A state management pattern/library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. https://vuex.vuejs.org/
  • 20. WHEN SHOULD I USE IT? There's a good quote from Dan Abramov, the author of Redux: Flux libraries are like glasses: you’ll know when you need them. https://vuex.vuejs.org/
  • 21. WHEN SHOULD I USE IT? It's a trade-off between short term and long term productivity. If you jump right into Vuex, it may feel verbose and daunting. But if you are building a medium-to-large-scale SPA, chances are you have run into situations that make you think about how to better handle state outside of your Vue components, and Vuex will be the natural next step for you. https://vuex.vuejs.org/
  • 23. INSTALL VUEX or <script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script> npm install --save vuex # or yarn add vuex # https://yarnpkg.com/ # or npx @vue/cli add vuex # https://cli.vuejs.org/ import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); https://vuex.vuejs.org/installation.html
  • 24. CONFIGURE VUEX Creating the store // src/store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { /* ... */ }, mutations: { /* ... */ }, }); https://vuex.vuejs.org/guide/
  • 25. CONFIGURE VUEX Enabling this.$store inside Vue components // src/main.js // ... import store from './store'; new Vue({ store, // same as `store: store` // ... }); https://vuex.vuejs.org/guide/
  • 26. CONCEPTS: STATE Creation new Vuex.Store({ state: { count: 0 }, // ... }); https://vuex.vuejs.org/guide/state.html
  • 27. CONCEPTS: STATE Basic usage <div> {{ $store.state.count }} {{ count }} </div> computed: { count () { return this.$store.state.count; } } https://vuex.vuejs.org/guide/state.html
  • 28. CONCEPTS: STATE mapState usage import { mapState } from 'vuex'; export default { // ... computed: mapState({ count: state => state.count, countAlias: 'count', // to access local state with `this` countPlusLocalState (state) { return state.count + this.localCount; } }) }; https://vuex.vuejs.org/guide/state.html
  • 29. CONCEPTS: STATE mapState usage simplified is the same as mapState({ count: state => state.count }) mapState([ 'count' ]) https://vuex.vuejs.org/guide/state.html
  • 30. CONCEPTS: STATE mapState usage with other computed values computed: { ...mapState({ // ... }), localComputed () { /* ... */ } } https://vuex.vuejs.org/guide/state.html
  • 31. CONCEPTS: GETTERS Getters are like "computed" values for a Vuex store Creation const store = new Vuex.Store({ state: { count: 0 }, getters: { countIsEven: state => { return state.count % 2 === 0; } } }); https://vuex.vuejs.org/guide/getters.html
  • 32. CONCEPTS: GETTERS Basic usage <div> {{ $store.getters.countIsEven }} {{ countIsEven }} </div> computed: { countIsEven () { return this.$store.getters.countIsEven; } } https://vuex.vuejs.org/guide/getters.html
  • 33. CONCEPTS: GETTERS mapGetters usage import { mapGetters } from 'vuex'; export default { // ... computed: mapGetters({ countIsEvenAlias: 'countIsEven' }) }; https://vuex.vuejs.org/guide/getters.html
  • 34. CONCEPTS: GETTERS mapGetters advanced usage computed: { ...mapState(['count']), ...mapGetters(['countIsEven']), localComputed () { /* ... */ } } https://vuex.vuejs.org/guide/getters.html
  • 35. CONCEPTS: MUTATIONS Committing a mutation is the only way to actually change state in a Vuex store. Creation const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state, payload=1) { state.count += payload; } } }); https://vuex.vuejs.org/guide/mutations.html
  • 36. CONCEPTS: MUTATIONS Basic usage methods: { increment (value) { return this.$store.commit('increment', value); } } https://vuex.vuejs.org/guide/mutations.html
  • 37. CONCEPTS: MUTATIONS mapMutations usage import { mapMutations } from 'vuex'; export default { // ... methods: { ...mapMutations([ 'increment' ]), ...mapMutations({ add: 'increment' }) } }; https://vuex.vuejs.org/guide/mutations.html
  • 38. MUTATIONS MUST BE SYNCHRONOUS Why? Because we need to have a "before" and "a er" snapshots of the state. If we introduce a callback inside a mutation, it makes that impossible. The callback is not called yet when the mutation is committed, and there's no way to know when the callback will actually be called. Any state mutation performed in the callback is essentially un-trackable! https://vuex.vuejs.org/guide/mutations.html
  • 39. CONCEPTS: ACTIONS Actions are similar to mutations, with a few differences: Instead of mutating the state, actions commit mutations. Actions can contain arbitrary asynchronous operations. https://vuex.vuejs.org/guide/actions.html
  • 40. CONCEPTS: ACTIONS Creation const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state, payload=1) { state.count += payload; } }, actions: { incrementAsync (context, payload) { setTimeout(() => { context.commit('increment', payload); }, 1000); } } }); https://vuex.vuejs.org/guide/actions.html
  • 41. CONCEPTS: ACTIONS API call example const store = new Vuex.Store({ actions: { async getRecords (context) { context.commit('getRecordsRequest'); try { const results = await axios.get('/api/records/'); context.commit('getRecordsSuccess', results.data); } catch (error) { context.commit('getRecordsFailure', error); } } } }); https://vuex.vuejs.org/guide/actions.html
  • 42. CONCEPTS: ACTIONS Context object context.commit to commit a mutation context.state access the state context.getters access the getters context.dispatch to call other actions https://vuex.vuejs.org/guide/actions.html
  • 43. CONCEPTS: ACTIONS mapActions usage import { mapActions } from 'vuex'; export default { // ... methods: { incrementAsyncLocal (value) { return this.$store.dispatch('incrementAsync', value) .then( /* ... */); } ...mapActions(['incrementAsync']), ...mapActions({ addAsync: 'incrementAsync' }) } }; https://vuex.vuejs.org/guide/actions.html
  • 44. EXAMPLE APP Creating the store, default state // src/store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export const defaultState = { availableNumbers: [...Array(90).keys()] .map((i) => i + 1), extractedNumbers: [], };
  • 45. EXAMPLE APP Creating the store, getters // src/store/index.js export const getters = { ascendingExtractedNumbers(state) { return [...state.extractedNumbers].sort((a, b) => a - b); }, };
  • 46. EXAMPLE APP Creating the store, mutations // src/store/index.js export const mutations = { extractNumber(state) { const index = Math.floor( Math.random() * state.availableNumbers.length); const extracted = state.availableNumbers .splice(index, 1); state.extractedNumbers = state.extractedNumbers .concat(extracted); }, };
  • 47. EXAMPLE APP Creating the store, composing // src/store/index.js export default new Vuex.Store({ state: defaultState, getters, mutations, });
  • 48. EXAMPLE APP Home component, data and computed function @@ src/views/Home.vue - data() { - return { - availableNumbers: [...Array(90).keys()].map((i) => i + - extractedNumbers: [], - }; - }, computed: { - ascendingExtractedNumbers() { - return [...this.extractedNumbers].sort((a, b) => a - b) - }, + ...mapState(['availableNumbers', 'extractedNumbers']), + ...mapGetters(['ascendingExtractedNumbers']), },
  • 49. EXAMPLE APP Home component, method @@ src/views/Home.vue - <button @click="handleExtract">Extract</button> + <button @click="extractNumber">Extract</button> methods: { - handleExtract() { - const index = Math.floor(Math.random() * this.available - const extracted = this.availableNumbers.splice(index, 1 - this.extractedNumbers = this.extractedNumbers.concat(ex - }, + ...mapMutations(['extractNumber']), },
  • 50. MOVING TO VUEX STORE FLOW Vue.js component Vuex store Map in data state computed computed getters computed sync methods mutations methods async methods actions methods
  • 51. STORE TESTS Default state import { defaultState } from '@/store'; test('should have the default state', () => { expect(defaultState).toEqual({ availableNumbers: [...Array(90).keys()].map((i) => i + 1), extractedNumbers: [], }); });
  • 52. STORE TESTS Getters import { getters } from '@/store'; const { ascendingExtractedNumbers } = getters; test('ascendingExtractedNumbers', () => { expect( ascendingExtractedNumbers({ extractedNumbers: [12, 56, 34] }) ).toEqual([12, 34, 56]); });
  • 53. STORE TESTS Mutations import { mutations } from '@/store'; const { defaultState, extractNumber } = mutations; test('ascendingExtractedNumbers', () => { jest.spyOn(global.Math, 'random') .mockReturnValueOnce(0.123456789) .mockReturnValueOnce(0.987654321); const state = { ...defaultState }; expect(state.availableNumbers).toHaveLength(90); // ...
  • 55. STORE TESTS Actions Keep in mind this sample action // export const actions = { async getRecords (context) { context.commit('getRecordsRequest'); try { const results = await axios.get('/api/records/'); context.commit('getRecordsSuccess', results.data); } catch (error) { context.commit('getRecordsFailure', error); } } // };
  • 56. STORE TESTS Actions Mocking calls using jest .mockReturnValue(value) for mocking sync results .mockResolvedValue(value) for mocking async results with success .mockRejectedValue(value) for mocking async results with failure import axios from 'axios'; import { actions } from '@/store'; jest.mock('axios', () => ({ get: jest.fn(), })); https://jestjs.io/docs/mock-functions
  • 57. STORE TESTS Actions Mocking axios success const { getRecords } = actions; test('getRecords success', async () => { const commit = jest.fn(); axios.get.mockResolvedValue({ data: 'ok' }); await getRecords({ commit }); expect(commit).toHaveBeenCalledWith('getRecordsRequest'); expect(axios.get).toHaveBeenCalledWith('/api/records/'); expect(commit).toHaveBeenCalledWith( 'getRecordsSuccess', 'ok'); });
  • 58. STORE TESTS Actions Mocking axios failures const { getRecords } = actions; test('getRecords failure', async () => { const commit = jest.fn(); axios.get.mockRejectedValue('my error'); try { await getRecords({ commit }); // Fail test if above expression doesn't throw anything expect(true).toBe(false); } catch (error) { expect(commit).toHaveBeenCalledWith('getRecordsRequest'); expect(axios.get).toHaveBeenCalledWith('/api/records/'); expect(commit).toHaveBeenCalledWith( 'getRecordsFailure', 'my error'); } });
  • 59. COMPONENT TESTS USING ORIGINAL STORE (NOT SUGGESTED) import { shallowMount } from '@vue/test-utils'; import Home from '@/views/Home.vue'; import store from '@/store'; test('snapshot test with default props', () => { const wrapper = shallowMount(Home, { store }); expect(wrapper).toMatchSnapshot(); }); https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 60. COMPONENT TESTS USING ORIGINAL STORE (NOT SUGGESTED) Pros fast and easy, store implementation ready-to-use Cons less control over store mocking and external calls https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 61. COMPONENT TESTS MOCKING THE STORE import { shallowMount, createLocalVue } from '@vue/test-utils' import Vuex from 'vuex'; import Home from '@/views/Home.vue'; const localVue = createLocalVue(); localVue.use(Vuex); // ... https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 62. COMPONENT TESTS MOCKING THE STORE // ... describe('Home.vue', () => { let state, getters, mutations, actions, store; beforeEach(() => { state = { count: 0 }; getters = { getter1: () => 'mocked return value' }; mutations = { mutation1: jest.fn() }; actions = { action1: jest.fn() }; store = new Vuex.Store({ state, getters, mutations, actions }); }); // ... https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 63. COMPONENT TESTS MOCKING THE STORE // ... test('snapshot test with default props', () => { const wrapper = shallowMount(Home, { store, localVue }); expect(wrapper).toMatchSnapshot(); // ... }); }); https://vue-test-utils.vuejs.org/guides/using-with-vuex.html
  • 64. RECAP What we learned today? Why use Vuex Install and configure a Vuex store Test store and components