Must there be a type assertion in non-initial reducers? - cyclejs

Since the type of of a reducer is defined as
export type Reducer<T> = (state: T | undefined) => T | undefined;
In my reducers that are not the initial reducer, I must declare
state = state as State
Am I missing something, or is this considered a minor inconvenience, please?

Non-initial reducers can be typed (in TypeScript) as (state: T) => T and these will be compatible with the Reducer<T> type found in the library. Here is an example from my codebase, the first snippet is an initial reducer that needs to treat the case for undefined:
const initReducer$ = xs.of(function initReducer(prev?: State): State {
if (prev) {
return prev;
} else {
return {
selfFeedId: '',
currentTab: 0,
};
}
});
This second snippet is a non-initial reducer where I am sure the previous state is not undefined:
const setSelfFeedId$ = ssbSource.selfFeedId$.map(
selfFeedId =>
function setSelfFeedId(prev: State): State {
return {...prev, selfFeedId};
},
);
Notice that when these streams are merged, the resulting type can be Stream<Reducer<State>> with no casting involved:
const reducer$: Stream<Reducer<State>> = xs.merge(initReducer$, setSelfFeedId$);

Related

Why does Pinia state mutate when using getters?

I would expect that when calling a getter from the store and assigning it to a variable, any changes to that variable would not result in the store state being updated, as I'm not calling the state directly.
This is true for primitive data, but not for object references.
<script setup>
import { ref } from 'vue'
import { useStudyStore } from '#/study/studyStore'
const StudyStore = useStudyStore()
let string = ref(StudyStore.getString)
string.value = 'bar2' // Does NOT update store
let object = ref(StudyStore.getObject)
object.value.a = 2 // DOES update store
const simpleArray = ref(StudyStore.getSimpleArray)
simpleArray.value[0] = 5 // DOES update store
</script>
// Store
export const useStudyStore = defineStore('study', {
state: () => ({
string: 'bar',
object: { a: 1 },
simpleArray: [1, 2, 3, 4],
}),
getters: {
getString(state) {
return state.string
},
getObject(state) {
return state.object
},
getSimpleArray(state) {
return state.simpleArray
},
},
})
I understand that this is due to how referencing works in Javascript, but I expected Pinia to handle this in some way to prevent accidental updating of the store. Is it really the case that any time we want to protect a non-primitive state value in the store from being mutated we need to parse/stringify them?
const copy = JSON.parse(JSON.stringify(StudyStore.getSimpleArray))
This is caused by the simpleArray being deeply reactive
Generally, you'd expect that everything in store is deeply reactive
If you want to explicitly remove reactivity, use
// the object will be non-reactive, you'll have to `store.simpleArray = store.simpleArray` to update it
simpleArray: markRaw([1, 2, 3, 4])
// the returned value would be non-reactive, but would stealthy change reactive object
getSimpleArray() {
return toRaw(this.simpleArray)
}
// will disallow changes on TS level
getSimpleArray() {
return this.simpleArray as DeepReadonly<typeof this.this.simpleArray>
}
// will disallow changes on proxy level
getSimpleArray() {
return deepReadonly(this.simpleArray)
}
(readonlys may be not is #vue but in #vueuse/core or whatever)
why deeply reactive, say you have deep options
options: Record<someId, { colorOfSomeId: string }>
obviously expected to be deep reactive

Vuex getter returns undefined value

I'm trying to write a getter for my state the problem is that it returns an undefined value but I'm 100% sure that in articleEan is an object that has an Are_EanNr value of 1234567.
This is the getter I'm writing is supposed to return the first object in the articleEan Array that has the same EanNr as the parameter.
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
Where's my mistake?
After Changing it to:
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode),
Problem still occurs. This is how I'm calling the getter from a component:
const getters = {
...useGetters('vm', [
'orderRow',
'customer',
'article',
'additionalData',
'findArByEan',
]),
...useGetters('user', ['user']),
};
const Ar = getters.findArByEan.value(eancode.value); // Ar = undefined
Edit:
When looping over the state I'm getting just the indices of the object in array.
log('ArtEan:', artEan.value); // correct output => Array with 38 objects
for(const item in artEan.value) {
log(item); // Logs just the index of array
}
Your second arrow function does not return anything so it's undefined
const getters = {
findArByEan: state => {
return (eancode) => { // logging eancode results in 1234567
return state.articleEan.find(item => {
return item.Are_EanNr === eancode
})
}
}
}
You can also do it this way without any return.
const getters = {
findArByEan: (state) => (eancode) => state.articleEan.find(item => item.Are_EanNr === eancode)
}
I would recommand reading the arrow function documentation.
For example, those 2 functions works the same but one is tidier ;)
const numberSquare1 = (number) => {
return number * number
}
const numberSquare2 = (number) => number * number
console.log(numberSquare1(2))
console.log(numberSquare2(2))

How can I add new values to an array object of state using Vuex?

My code like this :
<template>
...
...
</template>
<script>
export default {
...
methods: {
...mapActions([
'getDataDoctor',
'getDataSchedule'
]),
async visibilityChanged(isVisible, entry, item) {
let param = {
hospitalId: item.hospital_id,
doctorId: item.doctor_id
}
await this.getDataSchedule(param)
let data = this.dataDoctor
for (let key in data) {
this.$set(data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id), 'schedule', this.dataSchedule.schedule)
}
}
},
computed: {
...mapState({
dataDoctor: state => state.dataStore.dataDoctor,
dataSchedule: state => state.dataStore.dataSchedule
}),
},
}
</script>
If I console.log(this.dataDoctor), the result like this :
Or you can see the this.dataDoctor this : https://pastebin.com/yGjsTBjX
this.dataDoctor is state from vuex store. I want to add new value there. The name is schedule
I do like that. But there exist error like this :
Cannot set reactive property on undefined, null, or primitive value: undefined
Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'schedule' in undefined
How can I solve this problem?
This bit looks wrong:
for (let key in data) {
this.$set(data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id), 'schedule', this.dataSchedule.schedule)
}
From the sample data you posted this will only find something for (at most) one value of key. For the other values of key the find will return undefined and the $set will fail.
Try this:
for (const key in data) {
const entry = data[key].find(e => e.doctor_id === item.doctor_id && e.hospital_id === item.hospital_id)
console.log(`key is ${key}:`, entry)
if (entry) {
this.$set(entry, 'schedule', this.dataSchedule.schedule)
}
}
Of course it is also possible that none of the entries will match. The console logging I've included should help you to identify that scenario too.
I should add that it is usually regarded as best practice to mutate store state within a store mutation rather than directly inside a component.

How to mock values of constant in tested function

I have a problem with mocking constant in my test. I have a file with app configuration. There are just keys with some values nothing more.
appConfig.js
//imports
...
export const CASHING_AMOUNT_LIMIT = 50;
export const CASHING_DELETE_AMOUNT = 25;
...
and this is the reducer that I want to test:
reducer.js
import {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} from '../appConfig';
...
export const reducer = handleActions({
[REQUEST_DATA]: (state, action) => {
if (payload.count >= CASHING_AMOUNT_LIMIT) {
// do something with data if the condition is true
}
return {
...someState,
};
},
...
In my test, I want to change a value for CASHING_AMOUNT_LIMIT and check if reducer returns current store. And I don't know how to mock this variable in reducer.js. Here is my test:
...//imports
const mockValues = {
CASHING_AMOUNT_LIMIT: 10,
CASHING_DELETE_AMOUNT: 5,
};
jest.mock('../../appConfig.js', () => mockValues);
const {
CASHING_AMOUNT_LIMIT,
CASHING_DELETE_AMOUNT,
} = require('../../appConfig.js');
...
it('MY awesome test ', () => {
expect(CASHING_AMOUNT_LIMIT).toBe(10);
expect(CASHING_DELETE_AMOUNT).toBe(5);
// HERE is all ok CASHING_AMOUNT_LIMIT = 10 and the second variable is 5
// tests are OK
// ....
expect(storeWithCache.dispatch(requestFunction({ test: 'XX'})))
.toEqual(myStore);
...
In the end I use dispatch which call my reducer action and function in reducer.js it runs OK... but with old value for CASHING_AMOUNT_LIMIT it is still 50 (as in appConfig.js) and I need to set 10
Can somebody help me with mocking CASHING_AMOUNT_LIMIT in reducer.js?
This part
jest.mock('../../appConfig.js', () => mockValues);
Needs to be outside the describe block, under the imports.
I have a solution we have all mock in one file testenv.js and it is importing globally I put jest.mock('../../appConfig.js') there and it works!

Vuex Getters Method Style How to Return Function

According to the Vuex documentation, you can pass a payload to a getter method as long as the method returns a function.
https://vuex.vuejs.org/en/getters.html
I'm unclear on how I can format a function that returns a function.
In my case, I'd like to pass a product object to a getter method and use the product.price data along with data in the state to return a calculated price.
Here's a stripped down version of the approach I have currently:
const store = new Vuex.Store({
state: {
product: {
price: 12.99,
},
colors_front: 1,
colors_back: 0,
},
getters: {
printPrice: (state) => (product) => {
return (state, product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
}
},
}
}
When I try to access this getter in my component, I'm receiving the code of the function as a text string.
<template>
<div>{{ printPrice(product) }}</div>
</template>
export default {
computed: {
...mapState(['product']),
...mapGetters(['printPrice']),
}
}
Can anyone help me understand getters that return functions better? Is there a more appropriate way to do this?
I figured since I'm not actually mutating the state data, this method belonged better as a getter than a mutator, but I'm open to all suggestions.
Thanks!
Problem is that your getter is returning a function that also returns a function, so, when Vuex runs the function, it returns another one which seems to be cast to string (maybe by the template parser?).
Just make sure to return one single function with the expected output by changing this:
printPrice: (state) => (product) => {
return (state, product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
}
},
to this:
printPrice: (state) => (product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
},
That way we removed the wrapping function in the first level returning function.