I have code like this:
export const queryData = observable({
groupBy: "node"
});
autorun(()=> {
console.log(queryData.groupBy);
console.log(JSON.stringify(queryData));
});
And i have react components where i do :
#observer
class MyPage extends React.Component<IProps, {}>
{
handleSelect(value: string)
{
queryData.groupBy = value;
}
}
Other components that use queryData.groupBy show the updated value. Even the devtools shows the value being updated in the console log.
However, the autorun() is not fired on the property change. (its fired only once at the begining of the program)
ok apparently it was firing, just that enabling mobx-devtools made the output print in sub-hierarchy of some dev-tool related output
Related
I have implemented a watch within a Vue component that displays product information. The watch watches the route object of vue-router for a ProductID param to change. When it changes, I want to go get the product details from the back-end API.
To watch the route, I do this in Product.vue:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute();
async function getProduct(ProductID) {
await axios.get(`/api/product/${ProductID}`).then(..do something here)
}
// fetch the product information when params change
watch(() => route.params.ProductID, async (newID, oldID) => {
await getProduct(newId)
},
//watch options
{
deep: true,
immediate: true
}
)
},
}
The above code works, except that if a user navigates away from Product.vue, for example using the back button to go back to the homepage, the watch is triggered again and tries to make a call to the API using undefined as the ProductID (becaues ProductID param does not exist on the homepage route) e.g. http://localhost:8080/api/product/undefined. This causes an error to be thrown in the app.
Why does the watch trigger when a user has navigated away from Product.vue?
How can this be prevented properly? I can do it using if(newID) { await getProduct(newId) } but it seems counterintuitive to what the watch should be doing anyway.
UPDATE & SOLUTION
Place the following at the top replacing the name for whatever your route is called:
if (route.name !== "YourRouteName") {
return;
}
That will ensure nothing happens if you are not on the route you want to watch.
I ran into the same problem. Instead of watching the current route, use vue-router onBeforeRouteUpdate, which only gets called if the route changed and the same component is reused.
From https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})
const userData = ref()
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
watch registers the watcher inside an vue-internal, but component-independent object. I think it's a Map. So destroying the component has no effect on the reactivity system.
Just ignore the case where newID is undefined, like you already did. But to prevent wrapping your code in a big if block just use if(newID === undefined)return; at the beginning of your callback. If your ids are always truthy (0 and "" are invalid ids) you can even use if(!newID)return;.
well, in your use case the best approach would be to have a method or function which makes the api call to the server, having watch is not a really good use of it, because it will trigger whenever route changes and you do not want that to happen, what you want is simply get the productID from route and make the api call,
so it can be done with getting the productID in the created or mounted and make the api call!
I'm trying to understand the #Watch() part of the Stencil lifecycle docs: https://stenciljs.com/docs/component-lifecycle
To me, the illustration above does not clearly show from when #Watch() actually starts watching.
There seem to be cases where a #Watch() hook is triggered even before componentWillLoad(), for example when a Stencil component is used in React with React.createRef(). These cases are not always reproducible - meaning that it could be a race condition.
That's why I'd like to know from what particular point in time #Watch() becomes active?
As stated on stencil change #Prop() detection, #Watch decorator of a property triggers when the property value changes, but not when the property is initially set.
To capture the initialization you have to trigger the handler on componentWillLoad passing the property value.
#Watch('name')
onNameChanged(newValue: string, oldValue: string) {
this._name = newValue;
}
componentWillLoad(){
this.onNameChanged(this.name);
}
As can be seen in the graphic of your linked doc page, a #Watch() decorated method is called every time a change in the value of prop or state you watch occurs. That is independent from the willLoad/willRender/render/didRender/didLoad/didUpdate cycle that occurs when the component is attached to the DOM.
#Component({ tag: 'my-comp' })
export class MyComp {
#Prop() input = 'foo';
#State() repeated: string;
#Watch('input')
onInputChange() {
this.repeated = this.input + this.input;
}
componentWillLoad() {
this.onInputChange(); // can manually call the watcher here
}
render() {
return this.repeated;
}
}
<script>
const myComp = document.createElement('my-comp');
// currently `repeated` would be undefined because the component has not yet rendered
myComp.input = 'foobar'; // now `repeated` *should* be `foobarfoobar`
</script>
(saying *should* because I haven't tested this)
I have the following component, my component correctly displays the message from appState but when I change the value of appState the component isn't updated. I know I need to add an #observer, but how do you add it to a LitElement?
import { LitElement, html } from 'lit-element';
import { observable } from "mobx";
var appState = observable({
message: 'World'
});
class MyElement extends LitElement {
handleClick() {
appState.message = 'All';
}
render(){
return html`
<p>Hello, ${appState.message}</p>
<button #click=${this.handleClick}>Click me</button>
`;
}
}
customElements.define('my-element', MyElement);
LitElement itself is not such a good fit for mobx as changes which trigger a render need to be "full changes". Changing a property of an object is still the same object instance e.g. it will not trigger a render.
You can read the full story at https://open-wc.org/faq/rerender.html
You probably could use mobx autorun to trigger this.updateComplete() to force rerender but in that case, it's probably better to use a specialised lit-element version like https://github.com/adobe/lit-mobx.
Alternatively, a state machine could be a good fit in many cases as well. Take a look at https://www.npmjs.com/package/lit-robot.
How can I pass new state to a React Navigation function?
My code currently looks like this:
Simplified view of my parent class:
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this)
}
refresh() {
this.setState({
code: "bbb"
})
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh})
}
In the child class I then do the following:
this.props.navigation.state.params.refresh()
The issue I am facing:
Option 1: If I have the code as it currently is, it will not pass the new state value to the navigator because it is not in the render function
Option 2: If I place the code in the render function, it gives me the warning: "Cannot update during an existing state transition".
What am I doing wrong and how can I fix this?
Further details
I am using this main screen to load some of the details from an API on the web and store them in state. I want to be able to pass a refresh function to the second screen that I will be able to use to reload data from the API onto the main screen. Once the data is loaded back into the state on the main screen it should propagate back down to the second screen. This seems easy to do without using a navigator, but I am not sure how to do it with a navigator.
I am not currently wanting to use redux due to the learning curve, but would like to look into it some time in the future.
So you are trying to call refresh() method inside your child component. If you use this inside render function the refresh() method will be called repeatedly and it will give a warning: "Cannot update during an existing state transition".
If you keep the code as it is, it will update the parent class state. But that update will not be reflected when you accessing this.props.navigation.state.params.code. This will only give the value 'aaa'.
Option 1;
You can use redux and easily handle this scenario.
Option 2;
If you really want to know the value of the parent class state you can pass a function as navigation params to child which will return the value of the state.
Parent class.
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this);
this.getState = this.getState.bind(this)
}
refresh() {
this.setState({ code: "bbb" })
}
getState() {
return this.state.code;
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh, getState: this.getState })
}
Inside your child class use the following code to get the parent class state.
let parentClassState = this.props.navigation.state.params.getState();
I'm trying to use Vue for a little project.
I started with only client code. So when I did
const mv = new Vue({...});
I was able to access to the component from outside with a mv.
Now I'm using Vue.cli, so I define my component inside a mv.vue and then I have
export default {
data () {}
}
Here, how can I get the nme of my component?
Thanks for any reply or hint :)
You can get the name of the component , you can do this
this.$vnode.tag
If you want the parent component's name from its child do this
this.$parent.$vnode.tag
You can name your component like so:
export default {
name: 'mv',
data () {
return {}
}
}
But to access it you'd need to use the name that you import it with. For example:
import theVariableIWantItToBe from './mv.vue'
console.log(theVariableIWantItToBe)
To expand on the very good #vamsi-krishna answer, and update it, I've discovered that Vue now often puts a prefix on the $vnode.tag along the lines of vue-component-3-YourComponentName.
You can fix this by using the following code. And perhaps, just in case of a missing tag, fall back to the ID of the root element in the component.
Occasionally, Vue doesn't pass back a component at all in its errorHandler and warnHandler global events. So I've handled that scenario first.
if (!vm){
return '[unknown]'
}
if (vm.$vnode.tag) {
const res = vm.$vnode.tag
return res.replace(/vue-component-\d+-/i, '')
}
if (vm.$el.id) {
return vm.$el.id
}