How to understand the modules in the VUEX-STORE - javascript

For example, I have two lists: income and outcome. And I have two storages (one for income and one for outcome). I am adding these storages in modules into index.js.
I can make one repository for these income and outcome, display it in the list and calculate it. But I want to make a separate store for each.
Now the question is: How can I implement this correctly? I roughly did. But here I show and calculate only INCOME and that's it.
How to do it better? import via ...mapGetters two storages in one component to be calculate and show in the list? Or take data from two storages, and calculate everything in the index.js. Then take this data from the index.js? How do I use multiple modules in one component? I want to show the balance of income and outcome in one component and show in the list.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
import Vue from "vue";
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
},
};
export default income;
outcome.js
// import Vue from "vue";
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
};
export default outcome;
this is my component where i calculate balance
<template>
<div class="total-value" :style="balanceColor">
Balance: {{ totalBalance }}
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'TBalance',
computed: {
balanceColor: function() {
return {
color: this.totalBalance === 0 ? 'black' : this.totalBalance > 0 ? 'green' : 'red'
}
},
totalBalance() {
return Object.values(this.incomeList).reduce((acc, item) => acc + item.value, 0)
},
...mapGetters("income", ["incomeList"]),
},
methods: {
}
}
</script>

Here is an option for a more correct use of the store with modules.
I also put the calculation in the getter, which makes your component clean.
Try to bring the logic to the store so you can use the balance amount anywhere.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeBalance: state => {
// also, you can move this function into a separate file, and reuse
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default income;
outcome.js
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeBalance: state => {
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default outcome;
It is your component
<template>
<div class="total-value" :style="balanceColor">Balance: {{ incomeBalance }}</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
export default {
name: 'TBalance',
computed: {
...mapState('outcome', ['list']), // if you want a list here i added for example
...mapState('income', ['list']), // if you want a list here i added for example
...mapGetters('outcome', ['outcomeBalance']), // also added it for example
...mapGetters('income', ['incomeBalance']),
balanceColor() {
return {
color: this.incomeBalance === 0 ? 'black' : this.incomeBalance > 0 ? 'green' : 'red',
};
},
},
methods: {},
};
</script>

Related

How to properly access record with dynamic keys in Vuex

I am trying to query an array in Vuex store where product_id equals a dynamic value like in the below code but it returns undefined.
const store = new Vuex.Store({
state: {
products: {},
},
getters: {
getProductById:(state) => (id) => {
return state.products.find(product => product.data.product_id == id)
},
}
}
In component.vue
<template>
<div> {{price}}</div>
</template>
<script>
export default {
name: 'someName',
//props: ['orders'],
data(){
return{
price: '',
}
},
mounted(){
this.price = this.$store.getters.getProductByProductId(1)
}
}
</script>
Use a computed property that returns a function with id as parameter which returns the getter :
<template>
<div> {{price}}</div>
</template>
<script>
export default {
name: 'someName',
//props: ['orders'],
data(){
return{
price: '',
}
},
computed:{
productById(){
return (id)=>this.$store.getters.getProductByProductId(id)
}
},
mounted(){
this.price = productById(1).price
}
}
</script>

How to dynamically mutate "args" in Storybook v6 from the component's action?

Let's see we have the simple component ToggleButton:
const ButtonComponent = Vue.component('ButtonComponent', {
props: {
value: Boolean
},
methods: {
handleClick() {
this.$emit('toggle');
}
},
template: `
<button
:class="value ? 'on' : 'off'"
#click="handleClick"
>
Toggle
</button>`
});
And the story for that component:
import ToggleButton from './ToggleButton.vue';
export default {
title: 'ToggleButton',
component: ToggleButton,
argTypes: {
onToggle: {
action: 'toggle' // <-- instead of logging "toggle" I'd like to mutate `args.value` here
}
}
};
export const Default = (_args, { argTypes }) => ({
components: { ToggleButton },
props: Object.keys(argTypes),
template: `
<ToggleButton
:value="value"
:toggle="onToggle"
/>
`
});
Default.args = {
value: false
}
What I want to achieve is to handle toggle action inside the story and change value that I've used in Default.args object to change the button style by changing the class name from .off to .on.
I had the same exact issue, and kept looking for days, till I stumbled upon this github post:
https://github.com/storybookjs/storybook/issues/12006
Currently in my React (am sure vue approach will be similar), I do following:
import React from 'react';
import CheckboxGroupElement from '../CheckboxGroup';
import { STORYBOOK_CATEGORIES } from 'elements/storybook.categories';
import { useArgs } from '#storybook/client-api';
export default {
component: CheckboxGroupElement,
title: 'Components/CheckboxGroup',
argTypes: {
onChange: {
control: 'func',
table: {
category: STORYBOOK_CATEGORIES.EVENTS,
},
},
},
parameters: { actions: { argTypesRegex: '^on.*' } },
};
const Template = (args) => {
const [_, updateArgs] = useArgs();
const handle = (e, f) => {
// inside this function I am updating arguments, but you can call it anywhere according to your demand, the key solution here is using `useArgs()`
// As you see I am updating list of options with new state here
console.log(e, f);
updateArgs({ ...args, options: e });
};
return <CheckboxGroupElement {...args} onChange={handle} />;
};
export const CheckboxGroup = Template.bind({});
CheckboxGroup.storyName = 'CheckboxGroup';
CheckboxGroup.args = {
//Here you define default args for your story (initial ones)
controller: { label: 'Group controller' },
options: [
{ label: 'option 1', checked: true },
{ label: 'option 2', checked: false },
{ label: 'option 3', checked: false },
],
mode: 'nested',
};

Pass params to mapGetters in Vuex

Good day.
I use modules in my vue.js project. Now i need to get filtered data from getter, but don't undestand how provide params.
I want provide 'name' parameter to Getter. How i can do it in Component.vue ?
/*State in vuex*/
state: {
tempMessageValues: [
{ name: 'RU', status: 'none', selected: false },
{ name: 'BY', status: 'none', selected: false },
{ name: 'KG', status: 'none', selected: false }
]
}
/*Getters*/
import * as types from '../types';
export default {
[types.GETTERS.TEMP_MESSAGE_VALUES]: state => {
return state.tempMessageValues.find(country => country.name === name);
}
};
/*Types*/
export const GETTERS = {
TEMP_MESSAGE_VALUES: 'shared/TEMP_MESSAGE_VALUES'
};
/*Code in Component.vue*/
import * as types from "./store/types";
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
getTempMessValues: types.GETTERS.TEMP_MESSAGE_VALUES
})
}
};
The docs suggest using currying to pass params to a getter;
export default {
[types.GETTERS.TEMP_MESSAGE_VALUES]: state => name =>
state.tempMessageValues.find(country => country.name === name)
};
See https://vuex.vuejs.org/guide/getters.html#method-style-access for their example. You're essentially making your getter return a function the first time it's called.
Does that make sense?
A way would be to return a function from the getter, which you can then use to pass a parameter in
export default {
[types.GETTERS.TEMP_MESSAGE_VALUES]: state => {
return (name) => state.tempMessageValues.find(country => country.name === name);
}
};
Now you can pass a parameter to your getter by calling your function:
this.getTempMessValues('YourValue')

Testing functions in jest

I need some advice on testing functions in terms of how and what
say I have some state.
state = {
categories: [this is full of objects],
products: [this is also full of objects]
}
then I have this function:
filterProducts = () => {
return this.state.products.filter((product => (
product.categories.some((cat) => (
cat.title == this.state.chosenCategory
))
)))
}
this function filters the products array by working out if the products are part of the selected category.
how would you test this?
I've tried this
let productsStub = [
{id: 1, title: 'wine01', showDescription: false},
{id: 2, title: 'wine02', showDescription: false},
{id: 3, title: 'wine03', showDescription: false}
]
wrapper = shallow(<Menu
categories={categoriesStub}
products={productsStub}
/>);
it('should filter products when searched for', () => {
const input = wrapper.find('input');
input.simulate('change', {
target: { value: '01' }
});
expect(productsStub.length).toEqual(1);
});
what this test (I think) is saying, when I search for 01, I expect the product state (well the stub of the state) to filter and return only 1 result. however the test fails and says expected: 1 received: 3 i.e. the filtering isn't working.
I know I could also do wrapper.instance.filterProducts() but again, I'm not very comfortable on function testing in jest.
any advice? would be great to chat it through with someone
thanks
I replicated your problem statement, but not sure how are you maintaining the state model (props/state). But this might help. :)
Checkout the working example here: https://codesandbox.io/s/6zw0krx15k
import React from "react";
export default class Hello extends React.Component {
state = {
categories: [{ id: 1 }],
products: this.props.products,
selectedCat: 1,
filteredProducts: []
};
filterProducts = value => {
let filteredVal = this.props.products.filter(
product => product.id === parseInt(value)
);
this.setState({
filteredProducts: filteredVal
});
};
setValue = e => {
this.setState({
selectedCat: e.target.value
});
this.filterProducts(e.target.value);
};
render() {
return (
<div>
Filter
<input value={this.state.selectedCat} onChange={this.setValue} />
</div>
);
}
}
import { shallow } from "enzyme";
import Filter from "./Filter";
import React from "react";
let productsStub = [
{ id: 1, title: "wine01", showDescription: false },
{ id: 2, title: "wine02", showDescription: false },
{ id: 3, title: "wine03", showDescription: false }
];
let wrapper = shallow(<Filter products={productsStub} />);
it("should filter products when searched for", () => {
const input = wrapper.find("input");
input.simulate("change", {
target: { value: "1" }
});
expect(wrapper.state().filteredProducts.length).toEqual(1);
});

React Redux Reducer: 'this.props.tasks.map is not a function' error

I am making a React Redux example; however, I ran into an issue and get the error below:
TypeError: this.props.tasks.map is not a function
[Learn More]
I have tried many things and I cannot seem to understand why this is not working. I believe it is when the allReducers maps the tasks from the Tasks function. I have fixed this error back and forth but then it would complain it was undefined. I would fix that and loop back to this issue. Any help would be appreciated. Im sure I am making a simple mistake. Below are my following files
App.js
import React from 'react';
import TaskBoard from "../containers/task-board";
require('../../scss/style.scss');
const App = () => (
<div>
<h2>Task List</h2>
<hr />
<TaskBoard/>
</div>
);
export default App;
index.js
import {combineReducers} from 'redux';
import {Tasks} from './reducer-tasks';
const allReducers = combineReducers({
tasks: Tasks
});
export default allReducers
task-board.js
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {deleteTaskAction} from '../actions/ActionIndex';
import {editTaskAction} from '../actions/ActionIndex';
class TaskBoard extends Component {
renderList() {
return this.props.tasks.map((task) => {
if(task.status == "pending"){
return (<li key={task.id}>
{task.id} {task.description}
<button type="button">Finish</button>
<button type="button">Edit</button>
<button onClick={() => this.props.deleteTask(task)} type="button">Delete</button>
</li>
);
}
});
}
render() {
if (!this.props.tasks) {
console.log(this.props.tasks);
return (<div>You currently have no tasks, please first create one...</div>);
}
return (
<div>
{this.renderList()}
</div>
);
}
}
function mapStateToProps(state) {
return {
tasks: state.tasks
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators(
{
deleteTask: deleteTaskAction,
editTask: editTaskAction
}, dispatch)
}
export default connect(mapStateToProps,matchDispatchToProps)(TaskBoard);
reducer-tasks.js
const initialState = {
tasks: [
{
id: 1,
description: "This is a task",
status: "pending"
},
{
id: 2,
description: "This is another task",
status: "pending"
},
{
id: 3,
description: "This is an easy task",
status: "pending"
}
]
}
export function Tasks (state = initialState, action) {
switch (action.type) {
case 'ADD_TASK':
return Object.assign({}, state, {
tasks: [
...state.tasks,
{
description: action.text,
status: action.status
}
]
})
break;
case 'EDIT_TASK':
return action.payload;
break;
case 'DELETE_TASK':
return Object.assign({}, state, {
status: action.status
})
break;
}
return state;
}
actionindex.js
export const addTaskAction = (task) => {
return {
type: 'ADD_TASK',
text: "Here is a sample description",
status: "pending"
}
};
export const deleteTaskAction = (task) => {
return {
type: 'DELETE_TASK',
status: "deleted"
}
};
export const editTaskAction = (task) => {
return {
type: 'EDIT_TASK',
payload: task
}
};
It's because the function 'map' can only be used for arrays, not for objects.
If you print out this.props.tasks in the render function of task-board.js you'll see that it's an OBJECT which contains the tasks array, not the actual tasks array itself.
So to fix this it's quite easy, instead of:
return this.props.tasks.map((task) => {
it's
return this.props.tasks.tasks.map((task) => {
Then it works
According to your reducer composition, your initialState should be:
const initialState = [
{
id: 1,
description: "This is a task",
status: "pending"
},
{
id: 2,
description: "This is another task",
status: "pending"
},
{
id: 3,
description: "This is an easy task",
status: "pending"
}
]
Check server code if you're trying to render before getting the data. If that's the case, redux/react throws .map is not a function.
Always get the data, use middleware and then try to render client from server.
I faced the same in redux and the problem was I was trying to fetch data after rendering the client

Categories

Resources