componentDidUpdate creates infinite loop and not sure how to avoid it - javascript

Hi I am calling componentdidupdate in order to re-render my page with new ToDos like so
this.state = {
displayToDos: false,
displayLanding: true,
todo: {
title: '',
task: ''
},
todos: [],
createdToDo: null,
updateModal: false
}
}
async componentDidMount() {
this.fetchItems()
}
componentDidUpdate(prevState) {
if (prevState.todos !== this.state.todos) {
this.fetchItems()
}
}
fetchItems = async () => {
try {
const todos = await getItems()
this.setState({ todos })
console.log('set state of todos', this.state.todos)
} catch (err) {
console.error(err)
}
}
my intended behavior for my app is that the component mounts fetches the items and sets them to state, and then whenever I make a post, update or delete call the state of todos changes and it fires off the fetch call to re render the todos on the page. However I am getting an infinite loop, how do I fix this?

You can compare arrays like this:
let arraysEqual = JSON.stringify(todo) == JSON.stringify(todo1);
Or I will suggest you use this library Loadsh
you can use the .isEqual method to check the arrays.

Related

Static vue filtered Array is empty when I need it

I have a template that needs some non-reactive data in it from my Vuex store. However at the moment, I have to manually switch views to get the data to load. I am assuming I should not use mounted or created. If I use watch, then it basically becomes reactive again, and I only want to get this once.
data: function () {
return {
localreadmode: false,
myArray: null,
}
},
computed: {
...mapState({
myNodes: (state) => state.myNodes,
configPositions: (state) => state.configPositions,
configEmoji: (state) => state.configEmoji,
}),
nodes_filtered: function () {
return this.myNodes.filter((nodes) => {
return nodes.deleted == false
})
},
},
// this is to stop sync chasing bug
myArray: null,
created() {
this.$options.myArray = this.nodes_filtered
console.log(this.nodes_filtered)
// is empty unless I switch views
},
You could still use a watcher that runs only once via the vm.$watch API. It returns a method that can be called to stop watching the value, so your handler could invoke it when nodes_filtered[] is not empty.
export default {
mounted() {
const unwatch = this.$watch(this.nodes_filtered, value => {
// ignore falsy values
if (!value) return
// stop watching when nodes_filtered[] is not empty
if (value.length) unwatch()
// process value here
})
}
}
demo

Vue updates data without waiting for state file to update it

When the page is being loaded for the first time, vue component is not waiting for my custom store file to process it. I thought it might fix it with promises but I am not sure on how to do so on functions that do not really require extra processing time.
I am not including the entire .vue file because I know it surely works just fine. My store includes couple of functions and it is worth mentioning it is not set up using vuex but works very similarly. Since I also tested what causes the issue, I am only adding the function that is related and used in MainComp.
Vue component
import store from "./store";
export default {
name: "MainComp",
data() {
return {
isLoading: true,
storageSetup: store.storage.setupStorage,
cards: Array,
};
},
created() {
this.storageSetup().then(() => {
this.cards= store.state.cards;
});
this.displayData();
},
methods: {
displayData() {
this.isLoading = false;
},
}
My custom store.js file
const STORAGE = chrome.storage.sync;
const state = {
cards: []
};
const storage = {
async setupStorage() {
await STORAGE.get(['cards'], function (data) {
if (Object.keys(data).length === 0) {
storage.addToStorage('ALL');
// else case is the one does not work as required
} else {
data.cards.forEach((elem) => {
// modifies the element locally and then appends it to state.cards
actions.addCard(elem);
});
}
});
}
};
export default {
state,
storage
};
Lastly, please ignore the case in setupStorage() when the length of data is equal to 0. If there is nothing in Chrome's local space, then a cards is added properly(state.cards is an empty array every time the page loads). The problem of displaying the data only occurs when there are existing elements in the browser's storage.
How can I prevent vue from assuming cards is not an empty array but instead wait until the the data gets fetched and loaded to state.cards (i.e cards in MainComp)?
Sorry if the problem can be easily solved but I just lost hope of doing it myself. If any more information needs to be provided, please let me know.
Your main issue is that chrome.storage.sync.get is an asynchronous method but it does not return a promise which makes waiting on it difficult.
Try something like the following
const storage = {
setupStorage() {
return new Promise(resolve => { // return a promise
STORAGE.get(["cards"], data => {
if (Object.keys(data).length === 0) {
this.addToStorage("All")
} else {
data.cards.forEach(elem => {
actions.addCard(elem)
})
}
resolve() // resolve the promise so consumers know it's done
})
})
}
}
and in your component...
export default {
name: "MainComp",
data: () => ({
isLoading: true,
cards: [], // initialise as an array, not the Array constructor
}),
async created() {
await store.storage.setupStorage() // wait for the "get" to complete
this.cards = store.state.cards
this.isLoading = false
},
// ...
}

Can't perform a React state update on an unmounted component.This is a no-op

I have a below code :
export class ProductsCategory extends Component {
constructor() {
super();
// Title page
document.title = "products category";
// Varables url
let { getAllRows, changeOrder, changeTitle, changeIsActive } = new APIs().ProductsCategoryApi();
this.state = {
bindData: [],
loading: true,
getAllRowsUrl: getAllRows,
changeTitleUrl: changeTitle,
changeOrderUrl: changeOrder,
changeIsActiveUrl: changeIsActive,
};
// Bind table
this.bindTable();
}
bindTable = () => {
// Bind table
(async () => {
let response = await fetch(this.state.getAllRowsUrl);
// if HTTP-status is 200-299
if (response.ok) {
this.setState({
bindData: await response.json(),
loading: false
});
return;
}
Notify.bindData(false);
})();
}
reloadTable = () => { }
componentDidUpdate() {
this.bindTable();
}
render() { Some code };}
When change page, I see this error in console tab :
"Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method"
You need to cleanup the subscriptions. You can use AbortController to achieve that. Have a look at this post

React setstate return undefined on componentDidMount()

setState user property inside removeListener return undefined when i console.log() it inside component but when i check state in react developer tool user object from firebase it is there with actual value I want
state = {
coinList: {},
fav: [],
currentFavourite: '',
prices: [],
authed: false,
user: null
};
componentDidMount = () => {
this.removeListener = firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({
authed: true,
user
});
} else {
this.setState({
authed: false
});
}
});
this.ref = base.syncState('fav', {
context: this,
state: 'fav',
asArray: true
});}
If your console.log statement is inside the removeListener, I'd suspect that state hasn't been updated by the time console.log is called.
setState is asynchronous, so it's been updated in the background whilst the next statements are being.
You can provide setState with a function or statement that is only called once setState is completed....
this.setState({ user }, this.someFunction())
or simply...
this.setState({ user }, console.log(this.state.user));

Vuex Mutation running, but component not updating until manual commit in vue dev tools

I have a vue component that I can't get to update from a computed property that is populated from a service call.
Feed.vue
<template>
<div class="animated fadeIn">
<h1 v-if="!loading">Stats for {{ feed.name}}</h1>
<h2 v-if="loading">loading {{ feedID }}</h2>
</div>
</template>
<script>
export default {
data: () => {
return {
feedID: false
}
},
computed: {
feed(){
return this.$store.state.feed.currentFeed
},
loading(){
return this.$store.state.feed.status.loading;
}
},
created: function(){
this.feedID = this.$route.params.id;
var fid = this.$route.params.id;
const { dispatch } = this.$store;
dispatch('feed/getFeed', {fid});
}
}
</script>
That dispatches 'feed/getFeed' from the feed module...
feed.module.js
import { feedStatsService } from '../_services';
import { router } from '../_helpers';
export const feed = {
namespaced: true,
actions: {
getFeed({ dispatch, commit }, { fid }) {
commit('FeedRequest', {fid});
feedStatsService.getFeed(fid)
.then(
feed => {
commit('FeedSuccess', feed);
},
error => {
commit('FeedFailure', error);
dispatch('alert/error', error, { root: true });
}
)
}
},
mutations: {
FeedRequest(state, feed) {
state.status = {loading: true};
state.currentFeed = feed;
},
FeedSuccess(state, feed) {
state.currentFeed = feed;
state.status = {loading: false};
},
FeedFailure(state) {
state.status = {};
state.feed = null;
}
}
}
The feedStatsService.getFeed calls the service, which just runs a fetch and returns the results. Then commit('FeedSuccess', feed) gets called, which runs the mutation, which sets state.currentFeed=feed, and sets state.status.loading to false.
I can tell that it's stored, because the object shows up in the Vue dev tools. state.feed.currentFeed is the result from the service. But, my component doesn't change to reflect that. And there is a payload under mutations in the dev tool as well. When manually commit feed/feedSuccess in the dev tools, my component updates.
What am I missing here?
In the same way that component data properties need to be initialised, so too does your store's state. Vue cannot react to changes if it does not know about the initial data.
You appear to be missing something like...
state: {
status: { loading: true },
currentFeed: {}
}
Another option is to use Vue.set. See https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules...
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue
Hey for all the people coming to this and not being able to find a solution. The following was what worked for me:
Declaring base state:
state: {
mainNavData: [],
}
Then I had my action which is calling the now fixed mutation:
actions : {
async fetchMainNavData({ commit }) {
var response = await axios.get();
commit('setMainNavData', response));
},
};
Now my mutation is calling this updateState() function which is key to it all
mutations = {
setMainNavData(state, navData) {
updateState(state, 'mainNavData', navData);
},
};
This is what the updateState function is doing which solved my issues.
const updateState = (state, key, value) => {
const newState = state;
newState[key] = value;
};
After adding updateState() my data reactively showed up in the frontend and I didn't have to manually commit the data in Vue tools anymore.
please note my store is in a different file, so its a little bit different.
Hope this helps others!
Sometimes updating property that are not directly in the state is the problem
{
directprop: "noProblem",
indirectParent: {
"test": 5 // this one has a problem but works if we clone the whole object indirectParent
}
}
but it is a temporary solution, it should help you to force update the state and discover what is the real problem.

Categories

Resources