Pinia shared reactivity in stores - vue.js

Is it possible to share reactive state between stores in Pinia, for example:
export const useMainStore = defineStore('mainStore', {
state: () => ({
data: [{name: "some name", amount: useSomeStore().amount}]
}),
export const useSomeStore = defineStore('someStore', {
state: () => ({
amount: 0
}),
The idea is that useSomeStore.amount value is synced with the useMainStore data value.
So when I change the amount in useSomeStore to 5 I expect that data value in useMainStore will change accordingly to:
[{name: "some name", amount: 5}]
I know that I can subscribe to the store or use watchers but is there a better solution for this?
I made working solution using storeToRefs but not sure if there are drawbacks to this.
https://codesandbox.io/s/competent-breeze-wylkoj?file=/src/stores/tomato.ts

Remember pinia states are reactive objects.
Therefore, you can always set a computed on one of them which references another store's state.
Generic example:
const useStoreOne = defineStore('one', {
state: () => ({
foo: 'bar'
})
})
const useStoreTwo = defineStore('two', {
state: () => ({
foo: computed({
get() { return useStoreOne().foo },
set(val) { useStoreOne().foo = val }
})
})
})
Note: storeToRefs does the same as above. So you can write storeTwo as:
const useStoreTwo = defineStore('two', {
state: () => ({
foo: storeToRefs(useStoreOne()).foo
})
})
But it's kind of pointless. Why would you want to use useStoreTwo().foo anywhere instead of using useStoreOne().foo directly?
Make no mistake: the above pattern sets a two-way binding between the two store's foos. But, the second store's foo is useless. Anywhere you use it, you could be using the first one's foo directly.
On general principles, when you come across this pattern (or across the need for it), it should raise a flag: you're using state management because you want "one source of truth". Not more.

Related

Nuxt store getter not working, ID given to payload is not an Integer + Error: [vuex] do not mutate vuex store state outside mutation handlers

I am trying to make a product detail page. The detail page is named _id.
When opened the id is replaced with the product id. On opening the page the state is set with data fetched from an api.
After that i am trying to use a computed property that refers to a getter named getProduct() with an id (this.$route.params.id) in the payload.
This is how my _id.vue looks like:
methods: {
...mapActions("products", ["fetchProducts",]),
...mapGetters("products", ["getProduct",]),
},
async mounted() {
this.fetchProducts()
},
computed: {
product() {
return this.getProduct(this.$route.params.id)
}
}
This is how my store file named products.js looks like:
import axios from "axios"
export const state = () => ({
producten: []
})
export const mutations = {
setProducts(state, data) {
state.producten = data
}
}
export const getters = {
getProduct(state, id) {
console.log(id)
return state.producten.filter(product => product.id = id)
}
}
export const actions = {
async fetchProducts({ commit }) {
await axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
What works is creating the state, but when i try to use the getter something goes wrong.
As you can see i console.log() the id given to it. Which logs the following:
I also get the error: client.js?06a0:103 Error: [vuex] do not mutate vuex store state outside mutation handlers.
Which I'm not doing as far as I know?
**Note: **these errors get logged as much as the length of my state array is.
From the Vuex documentation:
Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
Like computed, getters does not support having arguments.
But there is a way to have "method-style access" to a getter: https://vuex.vuejs.org/guide/getters.html#property-style-access
You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Note that getters accessed via methods will run each time you call them, and the result is not cached.

can't get data from server to NuxtJS Store

this is my code :
export const state = () => ({
products: []
});
export const getters = {
getProducts: state => {
return state.products;
}
};
export const mutations = {
SET_IP: (state, payload) => {
state.products = payload;
}
};
export const actions = () => ({
async getIP({ commit }) {
const ip = await this.$axios.$get("http://localhost:8080/products");
commit("SET_IP", ip);
}
});
the server is working nicely but i just can't get the data into the store
First of all, I highly recommend you rename your action and mutation to something like getProducts and SET_PRODUCTS instead of ip. Also make sure you change the variable name inside the action. While this doesn't change any functionality, it makes your code easier to read.
Second, maybe add a console.log(ip) right after you define the const in the action and see if you're getting the data you want in there. In most cases you're going to want to assign ip.data to your variable.
Lastly, make sure you're calling the action somewhere in the code.
You should do it like this:
this.$store.dispatch('getIP'); // Using your current name
this.$store.dispatch('getProducts'); // Using my recommended name

redux-toolkit state change in extraReducer does not initiate rerender

I am trying to logout and purge the store at the same time, so on click I dispatch this:
dispatch({type: PURGE, key: 'root', result: () => { } });
Redux persist catches it, and reports purging the store. Great.
In another reducer I catch that dispatch, and remove my access token like this:
import { PURGE } from 'redux-persist/es/constants';
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAccessToken(state: AuthState, action: PayloadAction<Auth>): void {
state.accessToken = action.payload.accessToken;
state.expiresIn = action.payload.expiresIn;
},
},
extraReducers: {
[PURGE]: (state: AuthState, action: string): void => {
state.accessToken = initialState.accessToken;
state.expiresIn = initialState.expiresIn;
},
},
});
The PURGE reducer actually is called, and modifies the state, but still no re-rendering happens. so redux must not pick that up. But according to the docs the Redux toolkit uses a Proxy object for the state and does a comparison to see if it's modified.
Things I tried:
state = initialState;
and
state = { ...initialState };
Didn't work. The store works, and holds data, other actions work. How do I proceed?
EDIT: Further debugging revealed that my own reducer was called BEFORE the redux-persist reducer, and redux-logger reported that my reducer did not change the state at all.
I'm facing a similar issue (not re-rendering) and came by this thread today:
Seems like you can't replace state objects entirely.
From: https://redux-toolkit.js.org/usage/immer-reducers
Sometimes you may want to replace the
entire existing state, either because you've loaded some new data, or
you want to reset the state back to its initial value.
WARNING A common mistake is to try assigning state = someValue
directly. This will not work! This only points the local state
variable to a different reference. That is neither mutating the
existing state object/array in memory, nor returning an entirely new
value, so Immer does not make any actual changes.
const initialState = []
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
brokenTodosLoadedReducer(state, action) {
// ❌ ERROR: does not actually mutate or return anything new!
state = action.payload
},
fixedTodosLoadedReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return action.payload
},
correctResetTodosReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return initialState
},
},
})
So
state = initialState;
would be
return initialState;
This turned out to be the solution:
extraReducers: {
[PURGE]: (state: UserState, action: string): UserState => ({
...state,
...initialState,
}),
},
I don't understand why, as modifying the state object should work too, according to the documentation:
To make things easier, createReducer uses immer to let you write
reducers as if they were mutating the state directly. In reality, the
reducer receives a proxy state that translates all mutations into
equivalent copy operations.

vuex base state is shared between modules

I am using multiple vuex modules in nuxt store and I want to use the same base state in multiple modules like this:
// ~/utils/Sharedstore.js
export default {
state: {
byId: {},
allIds: [],
}
},
// ~store/entities/myEntity.js
import SharedStore from '~/utils/SharedStore';
export const state = () => ({ ...SharedStore.state });
But it doesn't work, whenever i mutate one state the state of all modules will be changed.
When I do this for all my modules it works:
// ~store/entities/myEntity.js
export const state = () => ({
byId: {},
allIds: [],
});
Problem is I would like to have the duplicated base states in one place (SharedStore.state). Why does it not work when importing and how can I fix it?
I found a fix:
export const state = () => JSON.parse(JSON.stringify(ModelStore.state));
Need to deep clone the object using JSON.parse(JSON.stringify(obj)) instead of spreading.
I guess the contents of byId and allIds still get used by reference when spreading?

Changing a nested object in Redux using spread operators when the keys are dynamic?

I am trying to store a nested object in redux, but I am using dynamic keys. Here is an example of what it would look like:
// In my Redux Reducer
const initialState = {
stuff: {
<dynamic_key>: { name: 'bob', title: 'mr' },
<dynamic_key>: { name: 'eve', title: 'ms' },
<dynamic_key>: { name: 'car', title: 'na' },
},
};
So I have a redux state called stuff that should hold my nested objects.
However, I cannot correctly save my data. I know react states all have to be immutable, so I am using the spread operator and Object.assign() to create a new object:
const reducer = ( state = initialState, action) => {
// ....
case UPDATE:
return { ...state,
stuff: {
Object.assign({}, action.key, {
name: action.name,
title: action.title
})
}
};
// ....
}
The above is trying to create/update the entire <dynamic_key>, using action.key as the dynamic_key, action.name as the name, and action.title as the title.
An extra tidbit is that if action.key (the dynamic key) doesn't already exist in the redux store stuff, then it should be created rather than error out.
What am I doing wrong? I think I am not using Object.assign() correctly, or not using the spread operator correctly?
EDIT:
Here is my redux action:
export const update = s => ({ type: "UPDATE", payload: {key: s.key, name: s.name, title: s.title} });
Using object spread operator
It seems like in you're case, you've got a couple additionally unnecessary steps. If stuff is supposed to be an object that contains your dynamic keys/value pairs, then you should have:
stuff: Object.assign({}, state.stuff, {[action.key]: {etc...}})
OR
stuff: {
...state.stuff
[action.key]: {name: etc...}
}
Keep in mind that every argument to Object.assign, must be an object. It seems like you are supplying the second argument a string.
I also assume you already have a compiler that allows you to safely use the object spread syntax.
EDIT: added state.stuff to both examples so as not to override previous properties.