vue constructor not having local state - vue.js

I have this code:
import Vue from 'vue'
import s from 'vue-styled-components'
import Test1x from './test1x'
export default Vue.extend({
name:'test1',
render(){
const Div=s.div`
`
const test1x1=new Test1x()
const test1x2=new Test1x()
const el=
<Div>
{test1x1.state.greeting}
{test1x2.state.greeting}
<button vOn:click={()=>test1x1.commit('change')}>change</button>
<button vOn:click={()=>test1x2.commit('change')}>change</button>
</Div>
return el
}
})
and test1x.js file is as follows:
import withStore from './withStore'
export default withStore({
state: {
greeting:'hola'
},
mutations: {
change(state){state.greeting='hello'}
}
})
and withStore.js file is as follows:
import Vue from 'vue'
export default ({ state, mutations }) => {
return Vue.extend({
data () {
return { state }
},
methods: {
commit (mutationName) {
mutations[mutationName](this.state)
},
},
})
}
Given that code, I assume each greeting will be changed by the corresponding button, separately, individually, but not, when I press a button all two greetings change. Anyone knows why? Thank you in advance.
And even more strange is that while at least code presented before is reactive, I mean, greeting change when pressing a button, code below it is not:
import Vue from 'vue'
import s from 'vue-styled-components'
import withStore from './withStore'
export default Vue.extend({
name:'test1',
render(){
const Div=s.div`
`
const Test1x=withStore({
state: {
greeting:'hola'
},
mutations: {
change(state){
state.greeting='hello'
}
}
})
const test1x1=new Test1x()
const test1x2=new Test1x()
const el=
<Div>
{test1x1.state.greeting}
{test1x2.state.greeting}
<button vOn:click={()=>test1x1.commit('change')}>change</button>
<button vOn:click={()=>test1x2.commit('change')}>change</button>
</Div>
return el
}
})
when pressing button nothing happens, greeting remains with hola instead of hello. Isn't that strange? Anyone knows why? Thanks again.
edit
thanks to #skirtle answer, I solved the issue doing this:
import Vue from 'vue'
import s from 'vue-styled-components'
import Test1 from './test1/test1'
import Test1x from './test1/test1x'
export default Vue.extend({
name:'app',
render(){
const Div=s.div`
`
const test1x1=new Test1x()
const test1x2=new Test1x()
//test1x1.commit('init')
test1x1.state={greeting:'hola'}
test1x2.state={greeting:'hola'}
console.log(test1x1.state)
const el=
<Div>
<Test1 test1x={test1x1}/>
<Test1 test1x={test1x2}/>
</Div>
return el
}
})
and test1.js being this:
import Vue from 'vue'
import s from 'vue-styled-components'
export default Vue.extend({
props:{
test1x:Object
},
name:'test1',
render(){
const Div=s.div`
`
const el=
<Div>
{this.test1x.state.greeting}
<button vOn:click={()=>this.test1x.commit('change')}>changes</button>
</Div>
return el
}
})
and test1x.js being this:
import withStore from './withStore'
export default withStore({
state: null,
mutations: {
change(state){state.greeting='hello'},
init(s){s={greeting:'hola'}
console.log(s)}
}
})
This works. The strange thing now is that if I uncomment test1x1.commit('init') I get an infinite loop, don't know why. If I then comment test1x1.state={greeting:'hola'} I don't get an infinite loop but I get an error that cannot read property greeting of null in test1.js. Anyone knows why this is happening? The thing is test1x1.commit('init') does not change the value test1x1.state, it remains null. Thanks.

Addressing the first problem first.
The problem starts here:
state: {
greeting:'hola'
},
The value of state points to a specific object. That object then gets passed around but at no point is a copy taken. The result is that both test1x1 and test1x2 will have the same object for state.
You can confirm this by adding a bit of console logging:
console.log(test1x1.state === test1x2.state)
The way Vuex handles this problem is to allow state to be a function, just like data:
state () {
return {
greeting:'hola'
}
},
Each time the state function is invoked it will return a new object.
As you aren't using Vuex you would need to ensure that you call the state function at the correct point to generate the relevant object. Something like this:
data () {
if (typeof state === 'function') {
state = state()
}
return { state }
},
So, to your second problem. I'm afraid I don't know what the problem is there. However, I very much doubt that 'when pressing button nothing happens'. It may not update the message but that isn't the same as 'nothing happens'. It should be relatively straightforward to add in some console logging at each stage and to establish exactly what does and doesn't happen. Once you've gathered all of that extra information about precisely what is happening it should be fairly simple to pinpoint precisely where the disconnect is occurring.
My suspicion would be that you've made some other changes to withStore that are causing this new problem. It could also be a file caching problem, so that the code you're running is not the code you think it is. Either way the extra logging should reveal all.
If you need further help with that then please update the question with the extra information gathered via console logging.
Update:
This is why the updated code causes an infinite rendering loop:
Inside the render function there is a call to test1x1.commit('init').
Inside commit it accesses the property this.state. This will add the property this.state as a rendering dependency for the component. It doesn't matter what the current value of this.state is, it's the property itself that is the dependency, not its current value.
On the next line it sets test1x1.state={greeting:'hola'}. This changes the value of the state property. This is the same state that has just been registered as a rendering dependency. As a rendering dependency has now changed the component will be re-added to the rendering queue, even though it hasn't finished the current rendering yet.
Eventually Vue will work its way through the rendering queue and get back to this same component. It will again call render to try to render the component. The previous steps will all occur again and so the component keeps being rendered over and over.
The bottom line here is that you shouldn't be initialising these data structures within the render function in the first place. There are various places you might create them but inside render does not appear to be appropriate based on the code you've provided.

Related

Handling default slot on custom component

Iam stugging on a problem to handle default slots with render() function.
I have two components, one that passes a string value innerHTML to my custom Component MySub. In MySub i wants to use the default slot to do further stuff with it.
My Parent:
import { defineComponent, h, VNode } from "vue";
import MySub from "./mySub.ts"
export default defineComponent({
render() {
return h(MySub, {}, 'innerHTML')
}
})
My Sub:
import { defineComponent, h, VNode } from "vue";
export default defineComponent({
data() {
return {
value: ''
}
},
mounted() {
if (this.$slots.default && this.$slots.default()[0]) this.value = <string>this.$slots.default()[0].children
},
render() {
return h('div', {}, [this.value + ' "here my added Stuff"'])
}
})
Now to my problem: When i code it like this (above or link below), i get a warning calls: Non-function value encountered for default slot. Prefer function slots for better performance. I know whats todo to get rid of these message and why its exists. Just add a function call to the value in my MyParent return h(MySub, {}, () => 'innerHTML').
But when i do this, it get the following message: Slot "default" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead. Also here, i know what the message wants to tell me, but i cant find a why to handle these problem.
I hope, i could explain my problem clear enough and somebody know what i can do.
Here is an Playground Example that reproduce exactly my problem.
Dont know its the correct way to reply my own question, but i found and resolved the problem...
The problem isnt occur in the parent component. You always should call default slots within a function call like: () => 'innerHTML' for better performance (like the warn message is calling) cause to not render it, when its empty.
So i have to search for the problem in the child...
And the problem was the function mounted()
m̶o̶u̶n̶t̶e̶d̶(̶)̶ ̶{̶
i̶f̶ ̶(̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶ ̶&̶&̶ ̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶(̶)̶[̶0̶]̶)̶ ̶t̶h̶i̶s̶.̶v̶a̶l̶u̶e̶ ̶=̶ ̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶(̶)̶[̶0̶]̶.̶c̶h̶i̶l̶d̶r̶e̶n̶
}̶,̶
In the render() function, i used this.value that is declared outside in the data() and assigned in the mounted(). So i just had to put it inside like below:
getDefaultSlotValue() {
if (this.$slots.default && this.$slots.default()[0]) this.value = <string>this.$slots.default()[0].children
},
render() {
return h('div', {}, [this.getDefaultSlotValue() + ' "here my added Stuff"'])
}
Iam not sure, but the problem seems to be no problem, when you call the MySub directly (to see in App.vue), cause the mounted() is normally called after rendering. But i dont know, whats vue exactly doing under the hood.
Here an working Playground example

Vuex freezing when modifying reactive state properties

I am fairly new to Vuex, and have ran into a problem I can't diagnose. My store is set up similarly to the Shopping example, and I've included the relevant module below.
The INIT action is called when the app loads, and everything functions fine. The LOOKUP action is later called from components, but freezes when calling the define mutation.
The current code is after trying several workarounds. Ultimately I'm trying to access state.pages from a component. I thought that the problem could've been because state.pages is an Object, so I made it non-reactive, and tried to make the component watch for changes in the pageCounter to retrieve the new page, but that didn't work.
I can include any other relevant information.
EDIT: Simplified the code to show more specific what the problem is.
store/modules/flashcards.js
// initial state
const state = () => ({
counter: 0,
})
// actions
const actions = {
}
// mutations
const mutations = {
increaseCounter(state) {
console.log(state.counter)
state.counter++; <----------- Code stops here
console.log(state.counter)
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
The component that accesses the store:
<template>
<div>
<md-button #click='increaseCounter'>Test</md-button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import FlashCardComponent from './FlashcardComponent'
export default {
computed: {
...mapState({
counter: state => state.flashcards.counter
})
},
methods: {
...mapMutations('flashcards', ['increaseCounter']),
</script>
In increaseCounter, the first console.log(state.counter) is printed, but the second one isn't. This is a very simple access pattern, so I would appreciate insight into why it's giving this error.
I figured out the issue. The strict flag was being used when creating the Store object. Everything worked once I removed that. As Tordek said, don't do this.
I suppose I need to figure out why that's a problem, as it points to the state being manipulated outside of a mutation.

Vue component not showing updated Vuex data

I realise this is a common issue with people new to vue and vuex, but I've been using it for two years now and thought I understood the ins and outs. Yet I'm stumped. There must be something I'm overlooking.
We've got a couple of complex models that used to have layouts hard-coded in the front end, and now some of those come from the backend instead, so I added a store module to handle that:
import { ActionTree } from 'vuex';
import { RootState } from '#/store';
import request from '../../services/request';
import layouts from '../../layouts';
export const types = {
FETCH_LAYOUT: `${MODULE_NAME}${FETCH_LAYOUT}`,
};
const initialState = {
layout: layouts,
};
const actions: ActionTree<LayoutState, RootState> = {
async [FETCH_LAYOUT]({ commit, state }, id) {
if (!state[id]) {
const layout = await request.get(`layout/${id}`);
commit(types.FETCH_LAYOUT, { layout, id });
}
},
};
const mutations = {
[types.FETCH_LAYOUT](state: any, { layout, id }) {
state.layout[id] = layout;
},
};
export default {
namespaced: true,
state: initialState,
getters: {},
actions,
mutations,
};
Everything here seems to work fine: the request goes out, response comes back, state is updated. I've verified that that works. I've got two components using this, one of them the parent of the other (and there's a lot of instances of the child). They're far to big to copy them here, but the import part is simply this:
computed: {
...mapState({
layout: (state: any) => {
console.log('mapState: ', state.layout, state.layout.layout[state.modelName]);
return state.layout.layout[state.modelName];
},
}),
},
This console.log doesn't trigger. Or actually it looks like it does trigger in the parent, but not in the children. If I change anything in the front-end code and it automatically loads those changes, the child components do have the correct layout, which makes sense because it's already in the store when the components are rerendered. But doing a reload of the page, they lose it again, because the components render before the layout returns, and they somehow don't update.
I'm baffled why this doesn't work, especially since it does seem to work in the parent. Both use mapState in the same way. Both instantiate the component with Vue.component(name, definition). I suppose I could pass the layout down as a parameter, but I'd rather not because it's global data, and I want to understand how this can fail. I've considered if maybe the state.layout[id] = layout might not trigger an automatic update, but it looks like it should, and the parent component does receive the update. I originally had state[id] = layout, which also didn't work.
Using Vue 2.6.11 and Vuex 3.3.0

How to do simple state management with components?

Vue documentation gives an example of simple state management, for a single-file app:
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
How to use the same mechanism for components?
I tried to move this concept of a minimal state manager to components in a codesandbox.io sandbox. It did not work and the more meaningful error, I believe, is
The "data" option should be a function that returns a per-instance value in component definitions.
Does this mean that components must be completely standalone and cannot rely on data managed outside of them?
try this Sandbox updated
in dataMaster.js
var store = {
state: {
message: "Hello!"
}
};
module.exports = store;
and component.vue
<template>
<div>
<h1>{{sharedState}}</h1>
</div>
</template>
<script>
import store from "./dataMaster";
export default {
name: "HelloWorld",
data() {
return {
privateState: {},
sharedState: store.state
};
}
};
</script>
The "data" option should be a function that returns a per-instance value in component definitions
It means that the data property you define in the component must be a function, and that function should return a per-instance value. You should, not any must here, and it can be tricked.
About the error, you got the error not because of state management, but because you forgot to export the function in dataMaster.js, so you couldn't import and use it in HelloWorld.vue. You got the error because you didn't return a function, that was the function part, not per-instance or anything related to state management.
To do the trick that I think you want, here it is: https://codesandbox.io/s/unruffled-carson-l3oui. You change the same source of truth directly from components, yet without tools like VueX or something. But it's tricky and is exactly what the error try to avoid, return a per-instance value. I don't know what the advantages and disadvantages of it yet, but at the end of the day, I think to do state management, we should use the standard recommended way from the prior people, like VueX, etc, just choose one from tons of them.
// dataMaster.js
const data = {
msg: "hello From dataMaster"
};
export default function dataMaster() {
return data // this is not "per-instance", they're the same across all instances
}
//Hello.vue
<template>
<div>
{{msg}}
<button #click="change">Change</button>
</div>
</template>
<script>
import dataMaster from "./dataMaster.js";
export default {
name: "HelloWorld",
data: dataMaster,
methods: {
change() {
this.msg = this.msg === "message1" ? "message2" : "message1";
}
}
};
</script>
"data" option should be a function, cause pure object could make data mess up. e.g when "sourceOfTruth" modified by componentA but you are focus on componentB, and you will confused. so please use "vuex" or "eventbus".
If you want to manage all data from global state. you should search and learn Vuex and Store Management.
Maybe using "Event Bus" is better for you example.
And you're wrong with this code, we always need to export the code.
export default function dataMaster() {
return {
msg: "hello from dataMaster"
};
}

Nuxt send data between 2 components

welcome to this topic. i recently tried to use the Nuxt framework to make my web-application but i ran into a problem.
In my default layout i have two components. a header component and a sidebar component. if i click on the hamburger icon in the header component the sidebar needs to get smaller or bigger depending on the hamburger icon state (true or false)
so to make it more complicated i don't want to use a prop to send it through the other component. i want to make it as a template so people can use it easy. can i transform a local component variable to a global variable other components can use?
so the code i have now is like this:
this is the index page
this is the header component
this is the sidebar component
as you can see i trigger the hamburgerstate on the header component page.
i want to access that state in the sidebarcomponent to so i can adjust the sidebar
the one thing that's IMPORTANT is that it needs to be as simple as possible so people who use this template later don't have to add unnecessary work
any possibilities this can work?
The simplest way to achieve a global variable is to set it as a state element and have a mutation for changing it. As your 'hambuger' is a boolean there is no need to pass parameters to the mutation making it all the easier.
You may want to have a named module in you store to handle this but I'll just put it in store/index.js for now.
export const state = () => ({
hamburger: true
})
export const mutations = {
changeHamburger (state) {
state.hamburger = !state.hamburger
}
}
Then in any page or component you can access that state element:
Component.vue
<script>
import { mapMutations } from 'vuex'
export default {
computed: {
hamburger () {
return this.$store.state.hamburger
}
},
methods: {
...mapMutations({
hamburgerChange: 'changeHamburger'
})
}
}
</script>
So this means you can now use the computed property 'hamburger' in your component and can change it by calling 'hamburgerChange', eg <v-btn #click="hamburgerChange">.