The child component is not called again if the component name is same - vue.js

I have a scenario where I have to call same component consecutively. First time when I call the component it loads but second time when I call the same component it doesn't reload the component.
I am using change state method to change the component.
ChangeState: function (newState) {
app.state = newState;
},
If I call some other component in between then it works correctly. Please suggest how can i solve this issue.

Try the below code. it will work if $ is not supported.
Vue.nextTick(function() {
app.state = newState;
})

No, it won't if Vue is watching for changes. One way around this is to set your state to null, then reset your component in the nextTick:
ChangeState(newState) {
this.state = null;
this.$nextTick(() => {
this.state = newState;
});
}
Here's the JSFiddle: https://jsfiddle.net/1spj1hzv/
Your code is a bit strange though, what is app? You don't seem to be referring to any internal data property (like I have in my answer), but your question suggests that app.state is reactive, so you may need to tweak this answer for your particular use case.

Related

Vue3 child component does not recreating, why?

I have made some sandbox code of my problem here:
https://codesandbox.io/s/clever-zeh-kdff1z
<template>
<div v-if="started">
<HelloWorld :msg="msg" #exit="exit" #remake="remake" />
</div>
<button v-if="!started" #click="started = !started">start</button>
</template>
<script>
import HelloWorldVue from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
data() {
return {
started: false,
msg: "Hello Vue 3 in CodeSandbox!",
};
},
methods: {
exit() {
this.started = false;
},
remake() {
this.msg = this.msg + 1;
//this code should recreate our child but...
this.exit();
this.started = true;
// setTimeout(() => {
// this.started = true;
// });
},
},
};
</script>
So! We have 2 components parent and child. The idea is simple - we have a flag variable in our parent. We have a v-if statement for this - hide / show an element depend on the flag value "false" or "true". After we toggle the flag - the child component should be recreated. This is the idea. Simple.
In our parent we have a button which will set the flag variable to "true" and our child will be created and will appear on our page.
Ok. Now we have 2 buttons inside our child.
One button is "exit" which is emit an event so the flag variable of parent will set to "false" and the elemint will disappear from our page(It will be destroyed btw). Works as charm. Ok.
The second button "remake". It emit event so the flag variable will be just toggled (off then on). Simple. We set to "false", we set to "true". So the current child should dissapear, and then imediatly will be created new one.
But here we are facing the problem! Ok, current child is still here, there is no any recreation, it just updates current one... So in child I have checked our lifecycle hooks - created and unmounted via console.log function. And the second button dont trigger them. Start->Exit->Start != Start->Remake.
So can anyone please explain me why this is happening? I cant figure it out.
Interesting thing, if you can see there is some asynchronous code commented in my demo. If we set our flag to "true" inside the async function the child will be recreated and we will see the created hook message but it seems like crutch. We also can add a :key to our component and update it to force rerender, but it also seems like a crutch.
Any explanations on this topic how things work would be nice.
Vue re-uses elements and components whenever it can. It will also only rerender once per tick. The length of a 'tick' is not something you should worry yourself about too much, other than that it exists. In your case the this.exit() and this.started = true statements are executed within the same tick. The data stored in this.started is both true in the last tick and the current tick as it does not end the tick in between the statements, and so nothing happens to your component.
In general you should think in states in Vue rather than in lifecycles. Or in other words: What are the different situations this component must be able to handle and how do you switch between those states. Rather than determining what to do in which point in time. Using :key="keyName" is indeed generally a crutch, as is using import { nextTick } from 'vue'; and using that to get some cadence of states to happen, as is using a setTimeout to get some code to execute after the current tick. The nasty part of setTimeout is also that it can execute code on a component that is already destroyed. It can sometimes help with animations though.
In my experience when people try to use lifecycle hooks they would rather have something happen when one of the props change. For example when a prop id on the child component changes you want to load data from the api to populate some fields. To get this to work use an immediate watcher instead:
watch: {
id: {
handler(newId, oldId) {
this.populateFromApi(newId);
},
immediate: true
}
}
Now it will call the watcher on component creation, and call it afterwards when you pass a different id. It will also help you gracefully handle cases where the component is created with a undefined or null value in one of the props you expect. Instead of throwing an error you just render nothing until the prop is valid.

Reload a React Native class component

I have a class component directions in my project. I navigate to another component from it using this.props.navigation.navigate(). Now the problem is that I want to navigate back to the same directions component but with passing new values, ie I want it to reload from scratch, defining state variables once again. How can I do it?
Using navigation.navigate() simply takes me back to the previous state the screen has been.
this.props.navigation.navigate('direction',{
riderLocation:this.state.rideInfo.location,
ride_id:this.state.ride_id,
});
And this is the componentDidMount of directions.
componentDidMount(){
alert('componentDidMount');
const {navigation,route}=this.props;
this.state.riderLocation = navigation.getParam('riderLocation');
this.state.ride_id= navigation.getParam('ride_id');
}
In the "directions" component, use "componentDidMount" method.
Inside "componentDidMount" method, call a function which updates the state value as desired.
Once you are redirected back to the "directions" component, then "componentDidMount" will run and the state will be updated.
====
Edit:
Try using componentDidUpdate() method in "directions" component.
componentDidUpdate(prevProps, prevState) {
if (prevProps.navigation.getParam('ride_id') !== this.props.navigation.getParam('ride_id')) {
const {
navigation,
route
} = this.props;
this.setState({
riderLocation: navigation.getParam('riderLocation'),
ride_id: navigation.getParam('ride_id')
})
}
}
Also instead of "this.state.riderLocation" and "this.state.ride_id" use this.setState in componentDidMount(), just like I have written in componentDidUpdate().

safe alternative to UNSAFE_componentWillMount?

I have an app that uses class based components. In the process of converting it from flow to typescript in line with current trends I realised that componentWillMount has ben deprecated and is no longer safe to use. I'm not sure what to do with it as this is in a class based component and converting it all into function components will take too long. I tried componentDidMount, but I got an error saying that setState should not be used in componentDidMount:
UNSAFE_componentWillMount() {
this._setupPanResponder();
this.setState({ date: this.props.date });
}
Is there a safe alternative to this that doesn't involve changing the component to a function component?
Can you move it to constructor?
Something like:
...
constructor(props: PropsType) {
super(props);
this._setupPanResponder();
this.state = { date: props.date };
}
...

From when is #Watch() active?

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)

React Native + Redux: How to subscribe to changes in state?

I have a component and I want to call a method checking the state whenever it changes. This is my component with a dummy method to demonstrate what I want to do (animate the view offscreen if onboarding.show === false):
export class Onboarding extends Component {
animateView() {
// i want to call this method when
// the state changes
// something like;
if (!this.props.onboarding.show) {
Animated.spring(...);
}
}
render() {
const { onboarding, finish } = this.props;
return (
<Animated.View>
...
</Animated.View>
);
}
}
...
export default connect(
state => {
return {
onboarding: state.onboarding,
};
},
dispatch => {
return {
};
}
)(Onboarding);
Is there a way to subscribe to the changes in state?
== UPDATE ==
as requested, here's what my slideOffScreen method does:
slideOffScreen() {
Animated.timing(this.state.offsetX, {
toValue: -Dimensions.get('window').width,
duration: 350,
easing: Easing.elastic(),
}).start();
}
The react-redux connect method wraps the component with a container component that is aware of the store's state changes. Whenever the state changes, connect re-renders the wrapped component (Onboarding in your case).
According to the redux docs:
Technically, a container component is just a React component that uses
store.subscribe() to read a part of the Redux state tree and supply
props to a presentational component it renders. You could write a
container component by hand, but we suggest instead generating
container components with the React Redux library's connect()
function, which provides many useful optimizations to prevent
unnecessary re-renders.
If your component doesn't re-rendered when the state changes, check if you're not mutating the state instead of replacing it. Redux checks if the state changed by shallowly comparing the old state, and the new state (comparing only the references, and not the values).
For example, to add an item to an array, you can't use array.push(item) because that won't create a new array, just mutate the existing one. Instead you'll have to use something like array.concat(item), which does.
To update objects, you can see in the redux docs under handling actios example, you can see that to create a new state:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.
Looks like this works:
componentWillReceiveProps(props) {
if (!props.onboarding.show) {
this.slideOffScreen();
}
}
not sure if there's a way to do it through the redux API