Data not refreshing from child component to parent - javascript

I have a child component that I add in links. Once completed I call the parent component to fetch the new links.
My links in the v-for aren't updating. They are only updating when I reload the page with the new entry. Upon submit, I want my child component to notify/call the parents fetchLinks function to update all the links on the screen (Which its not updating, unless I refresh page)
This is on the form success function from Child AddLinksComponent
// Get the new updated subscriptions
this.$parent.fetchLinks()
Parent Component
<div class="card card-body" v-for="link in links" v-bind:key="link.id">
<h2>{{ link.name }}</h2>
</div>
<add-links></add-links>
export default {
data() {
return {
links: [],
}
},
components: {
'add-links': AddLinksComponent,
},
methods: {
fetchLinks() {
fetch('/links/')
.then(res => res.json())
.then(res => {
this.links = res.data
}).catch(err => {
console.log('Error', err)
});
}
},
created() {
this.fetchLinks();
},
mounted() {
}
}

In the child component try to emit an event to the parent one when the task is done like :
this.$emit('fetch');
in the parent component :
<add-links #fetch="fetchLinks"></add-links>

Related

Component not re-mounting when new props is passed

I want to fetch data based on the name of the name of the doctor. On the 1st click on the Sales Overview tab, the component gets mounted and the data is fetched smoothly and I see all the charts get updated. However, when I use the Search button to fetch the data for a new doctor and then click on the Sales Overview tab, I don't see any fetch happening and I continue seeing the data of the previous MD.
Here is a screenshot of the search field and the tabs:
I'm passing the name of the doctor from the parent Search component to the SalesOverview1 child component like this:
{
this.state.tab_button_clicked === 'sales_overview' &&
<SalesOverview1
searchName = {this.state.searchName}
/>
}
Inside SalesOverview1, I'm fetching the data in componentDidMount and then updating the various charts. I'm also setting the state of it based on the props received from parent Search component. I'm keeping a default value of ["[101]Anna"] if there is nothing to pass from search component to child SalesOverview1 comp.
class SalesOverview1 extends Component {
state = {
page_id: 4,
hcp_id: 101,
sales_overview_data: [],
searchName: this.props.searchName.length === 0 ? ["[101]Anna"] : this.props.searchName
}
componentDidMount() {
console.log('Clicked on sales Overview!');
let page_id = this.state.page_id;
let hcp_id = parseInt(this.state.searchName[0].replace(/(^.*\[|\].*$)/g, ''));
console.log('state: ', this.state);
axios.post('/test-json', {
page_id: page_id,
hcp_id: hcp_id,
})
.then((res) => {
const dataRequest = res.data;
this.setState({ sales_overview_data: res.data });
}, (error) => {
console.log(error);
});
}
render() {
console.log('props data inside sales overview comp: ', this.props)
console.log('state inside sales overview comp: ', this.state)
return (
<>
<div class='row'>
{
this.state.sales_overview_data.length !== 0 &&
<ChartBox
data={this.state.sales_overview_data[401]}
/>
}
</div>
</>
Basically, I want to run the api request after I receive a new props in this component. I want to re-mount so that I can make the api request.
In the api fetch, I'm using a replace function to extract the number between the square brackets which is then used for fetching data:
Adding a key while passing props to child component did the trick.
{
this.state.tab_button_clicked === 'sales_overview' &&
<SalesOverview1
key={this.state.finalsearchName}
searchName = {this.state.searchName}
/>
}
I read that adding a key automatically re-mounts the child component if the props has changed.

reload vue component from outside

I have a laravel vue project that displays a list of items in a component. The component when mounted makes an ajax call to populate the data element. However, there's other items on the page (not in vue) that can add elements to the database, and I'd like to make sure the list is reactive in the component.
mounted() {
this.getTasks();
},
methods: {
getTasks() {
let self = this;
axios.get('/tasks').then(response => {
self.tasks = response.data;
})
.catch(function (error) {
console.log(error);
});
},
}
When the user does an action that would add a task to the list, is there a way to fire the getTasks method on the component from outside the component?
You can declare a global function (binding context to Vue component) when component is mounted:
mounted() {
windows.getTasks = this.getTasks.bind(this);
this.getTasks();
},
Then you can use it outside calling getTasks() or windows.getTasks()
You should use vuex actions.
Here's an example:
Vue.component('ChildB',{
template:`
<div class="child childB">
<h1> Score: {{ score }} </h1>
<button #click="changeScore">Change Score</button>
</div>`,
computed: {
score () {
return this.$store.getters.score
}
},
methods: {
changeScore () {
this.$store.dispatch('incrementScore', 3000)
}
}
})
full source:
https://codepen.io/tutsplus/pen/MXpPLz

How to Avoid re-rendering of the screens in a tabNavigator once I make a setState of a screenProps?

I am implementing push notifications in a react native project, where from the parent component of the application I need to pass parameters through the reactNavigation screenProps for a BottomTabNavigator. Once I receive a notification, and I change the status to update the parameter that will indicate to the child components that must be updated, a complete re-rendering of the application is made.
This is my code:
class AppLayout extends Component {
state = {
updatePushNotifications: false
}
handleActivatePushNotifications = () => {
this.handlePushNotificationMessageListener();
}
handlePushNotificationMessageListener = async () => {
this.notificationListener = firebase.notifications().onNotification((notification) => {
const { title, body } = notification;
console.log(notification);
console.log('notificationListener');
//SETSTATE FOR UPDATE CALLS IN CHILD COMPONENTS
this.setState({
updatePushNotifications: true
});
this.showAlert(title, body);
});
}
showAlert = (title, message) => {
Alert.alert(
title,
message,
[
{text: 'OK', onPress: () => console.log('OK Pressed')},
],
{cancelable: false},
);
}
// The entire navigation component is re-rendered once the setState is executed
render () {
return (
<Layout
screenProps={{
updatePushNotifications: this.state.updatePushNotifications
}}
/>
);
}
}
How can I prevent a re-render of the application when I update any of the parameters that I am passing through the screenProps?
Thanks in advance for any help
If you use state in your render method, it always re-render your app with state changes. You can define a variable in class and update it. Then pass it to prop. But be careful, because you can't see changes if you don't make forceUpdate.

Vue component doesn't update when state in store is updated

I'm trying to do a simple todo list in Vue but I want to abstract everything out and use a dummy REST API so I can start to get used to production-level projects in Vue and it's all making my head spin. GET, PUT, and POST requests seem to be working, but I can't figure out why the list of todos doesn't update automatically when I do a successful POST request to the back end.
I've got a TodoList component that loops through a todosFiltered() computed property to show the todos. The computed property refers back to the getter todosFiltered in the Vuex store. I also use the created() lifecycle hook here to dispatch an action in the store that makes the initial GET request and then populates an array called todos in the store when the page is first loaded. The getter todosFiltered in the store returns state.todos, so I assumed that when my component re-renders, it would have the new todos array from the state grabbed from todosFiltered, only that's not happening. What am I missing here? Any advice would be greatly appreciated.
TodoList.vue
(I know I'll have to work out a solution for the ids, it's on my list :p)
<template>
<div class="container">
<input v-model="newTodo" type="text" placeholder="What must be done?" class="todo-input" #keyup.enter="addTodo">
<transition-group name="fade" enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
<todo-item v-for="todo in todosFiltered" :key="todo.id" :checkAll="!anyRemaining" :todo="todo"></todo-item>
</transition-group>
<div class="extra-container">
<todos-filtered></todos-filtered>
</div>
</div>
</template>
<script>
import TodosFiltered from './TodosFiltered'
import TodoItem from './TodoItem'
export default {
name: 'todolist',
components: {
TodosFiltered,
TodoItem
},
data() {
return {
beforeEditCache: '',
newTodo: '',
idForTodo: 10,
}
},
// Methods
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) {
return
}
this.$store.dispatch('addTodo', {
id: this.idForTodo,
title: this.newTodo,
completed: false
})
this.newTodo = ''
this.idForTodo++
}
},
computed: {
todosFiltered() {
return this.$store.getters.todosFiltered
},
},
created() {
this.$store.dispatch('loadTodos')
},
}
</script>
store.js
export const store = new Vuex.Store({
state: {
filter: 'all',
todos: []
},
getters: {
todosFiltered(state) {
if (state.filter == 'all') {
return state.todos
} else if (state.filter == 'active') {
return state.todos.filter(todo => !todo.completed)
} else if (state.filter == 'completed') {
return state.todos.filter(todo => todo.completed)
}
return state.todos
},
showClearCompleted(state) {
return state.todos.filter(todo => todo.completed).length > 0
}
},
mutations: {
addTodo(state, todo) {
state.todos.push(todo)
},
setTodos(state, todos) {
state.todos = todos
},
},
actions: {
loadTodos(context) {
axios.get('http://localhost:3000/todos')
.then(r => r.data)
.then(todos => {
context.commit('setTodos', todos)
})
},
updateTodo(context, todo) {
axios.put('http://localhost:3000/todos/' + todo.id, {
"id": todo.id,
"title": todo.title,
"completed": todo.completed
})
},
addTodo(context, todo) {
axios.post('http://localhost:3000/todos', {
"id": todo.id,
"title": todo.title,
"completed": todo.completed
})
.then(todo => {
context.commit('addTodo', todo)
})
},
}
})
EDIT: Here's what's going on in Vue Dev Tools when I add a todo -- todos in the store's state gets updated immediately, and the todosFiltered computed property in the TodoList component ALSO reflects that -- but the new todo doesn't appear in the list! Strange.
A way to solve this can be to create what I like to call a refresh() method.
Basically, you will have a local list of todos in your data() method, the refresh() method will load all the todos from the store into the local todos list, every time you do an action, such as creating, deleting, or updating, you would call the refresh method to re-load the list for you.
So, in your TodoList.vue:
<template>
<todo-item v-for="todo in todosFiltered" :key="todo.id"></todo-item>
</template>
<script>
export default {
data() {
return {
// Where we store the local list of Todos
// so the component will react when we do something to it
todosFiltered: []
}
},
methods {
refresh() {
// Get the todo list from the store.
// which in turn will trigger a change event so the component
// react to what we did.
this.todosFiltered = this.$store.getters.todosFiltered;
},
addTodo() {
this.$store.dispatch('addTodo').then(() => {
// Refresh the list after adding a new Todo
this.refresh();
})
},
updateTodo() {
this.$store.dispatch('updateTodo').then(() => {
// Refresh the list after updating a Todo
this.refresh();
})
},
deleteTodo() {
this.$store.dispatch('deleteTodo').then(() => {
// Refresh the list after deleting a Todo
this.refresh();
})
}
},
created() {
this.$store.dispatch('loadTodos').then( () => {
// Refresh the list when first loaded after the Todos been saved in Vuex
this.refresh();
})
}
}
</script>
Don't actually delete what you already have and replace it with
this, just apply what's here to your code.
The problem is using a $store.getter on the v-for loop.
Try the following:
Set your computed to
todos() {
return this.$store.todos;
}
Change your v-for loop to use todo in todos
Add a v-if condition to the loop like v-if="filtered(todo)"
Create a new method called filtered (or whatever you prefer), and add your "filteredTodos" logic there, returning true/false as needed
If you need to share this code, you can always use a mixin and share it between your components
Hope this works for you

Setting props for components using v-for in Vue JS

I have a PhoneCard.vue component that I'm trying to pass props to.
<template>
<div class='phone-number-card'>
<div class='number-card-header'>
<h4 class="number-card-header-text">{{ cardData.phone_number }}</h4>
<span class="number-card-subheader">
{{ cardData.username }}
</span>
</div>
</div>
</template>
<script>
export default {
props: ['userData'],
components: {
},
data() {
return {
cardData: {}
}
},
methods: {
setCardData() {
this.cardData = this.userData;
console.log(this.cardData);
}
},
watch: {
userData() {
this.setCardData();
}
}
}
The component receives a property of userData, which is then being set to the cardData property of the component.
I have another Vue.js component that I'm using as a page. On this page I'm making an AJAX call to an api to get a list of numbers and users.
import PhoneCard from './../../global/PhoneCard.vue';
export default {
components: {
'phone-card': PhoneCard
},
data() {
return {
phoneNumbers: [],
}
},
methods: {
fetchActiveNumbers() {
console.log('fetch active num');
axios.get('/api').then(res => {
this.phoneNumbers = res.data;
}).catch(err => {
console.log(err.response.data);
})
}
},
mounted() {
this.fetchActiveNumbers();
}
}
Then once I've set the response data from the ajax call equal to the phoneNumbers property.
After this comes the issue, I try to iterate through each number in the phoneNumber array and bind the value for the current number being iterated through to the Card's component, like so:
<phone-card v-for="number in phoneNumbers" :user-data="number"></phone-card>
However this leads to errors in dev tools such as property username is undefined, error rendering component, cannot read property split of undefined.
I've tried other ways to do this but they all seem to cause the same error. any ideas on how to properly bind props of a component to the current iteration object of a vue-for loop?
Try
export default {
props: ['userData'],
data() {
return {
cardData: this.userData
}
}
}
Answered my own question, after some tinkering.
instead of calling a function to set the data in the watch function, all I had to do was this to get it working.
mounted() {
this.cardData = this.userData;
}
weird, I've used the watch method to listen for changes to the props of components before and it's worked flawlessly but I guess there's something different going on here. Any insight on what's different or why it works like this would be cool!

Categories

Resources