I have an object that when logged prints the following:
Object {
"Air Conditioning": false,
"Attic": false,
"Basement": false,
"Bathrooms": false,
"Bedrooms / Living Areas": false,
"Crawl Space": false,
}
I would like to setState using the above. I attempted the following:
componentDidMount() {
this.setAreaNamesInState(this.props.areas)
}
setAreaNamesInState(areaNames) {
let areaNamesList = {}
for (let area of areaNames) {
areaNamesList[area] = false
}
console.log('areaNamesList', areaNamesList)
this.setState(areaNamesList)
console.log('Attic', this.state['Attic'])
}
It doesn't seem to be working, as when I log Attic above it returns undefined.
The answers of other users are correct, you could do the following
this.setState({ areas: areaNamesList }, () => {
console.log('Attic', this.state.areas['Attic'])
})
The difference is you are trying to set the whole state object with your newly created object, which is a bad practice, while this answer updates a property called areas inside your state object with the provided data.
The console log will execute synchronously after the state is updated with the property areas, and log false
As a side note, maybe using componentDidMount is a bad idea if the prop is not provided the first time the component is created and rendered, since it's only executed the first time and never again, the property might be undefined by the time you need it.
Consider switching to other lifecycle methods.
Try with
this.setState({ areaNamesList }, () => {
console.log('Attic', this.state.areaNamesList['Attic'])
})
You're missing curly braces in your setState as this.state is an object.
this.setState({ areaNamesList })
Also worth mentioning that setState may not be completed before console.log('Attic', this.state.areaNamesList['Attic']). setState can take a callback that will be executed when it is complete as such:
this.setState({ Attic: areaNamesList }, () => {
console.log('Attic', this.state.areaNamesList["Attic"])
})
Related
I am trying to show some dynamic content in my component but somehow useEffect causes a infinite loop.
What can be the problem?
useEffect(() => {
retrieveLocalData('following').then((contacts) => {
setLocalData(JSON.parse(contacts));
});
}, [getLocalData]);
async function retrieveLocalData(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
console.log('test'); // infinite
Code: https://codepen.io/eneskul/pen/OJWEgmw
Updated Answer
The infinite loop is a result of the useEffect hook updating the same value that is triggering the hook to run in the first place.
Here's a simple example to illustrate the problem:
const [value, setValue] = useState({ foo: 'bar' });
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
const newValue = JSON.parse(result);
// `newValue` is a new object, even if its content is identical to `value`.
setValue(newValue);
});
}, [value]);
In this example, when value is set, it causes the useEffect hook to execute, which will asynchronously update value with a new object, which will cause the useEffect hook to execute again, and so on. Even though the contents of the objects are identical, the JSON.parse call creates a new object with a new reference.
You can prevent the infinite loop by doing a deep equality check of the two objects before updating the state. Using something like Lodash's isEqual function makes this pretty easy.
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
setValue((prev) => {
const newValue = JSON.parse(result);
// Do a deep comparison and only update state with new object if content is different.
return isEqual(prev, newValue) ? prev : newValue;
});
});
}, [value]);
In this example, the reference to value will only change if the contents of the objects are different.
However, this only explains what the problem is. I'm not sure what the right solution is for your problem, since it's not clear why the component only needs to load data from local storage into state when the state changes, but the state is only updated when it loads from local storage. There seems to be a "chicken or the egg" problem here. It feels like there should be something else that should trigger loading data from local storage into state, other than the data that was just loaded from local storage into state.
Previous Answer
The likely culprit here is getLocalData in the dependency list of the useEffect hook. If that is not a stable reference (i.e. the reference changes on each render), then it will cause the useEffect hook to execute, which will then trigger a state update, which will trigger a render, which will cause useEffect to execute again, which starts the whole thing over again.
In the sample code, it's not clear where getLocalData comes from. Wherever it comes from, you might consider wrapping it with the useCallback hook to create a stable reference. If it's just a typo and meant to be retrieveLocalData, then that is definitely the issue. Because retrieveLocalData is declared inside the component's render function, it will create a new instance of the function (with a new reference) on each render.
I would just move it inside the useEffect hook and eliminate the dependencies.
useEffect(() => {
AsyncStorage.getItem('following')
.then((contacts) => {
setLocalData(JSON.parse(contacts));
})
.catch((error) => {
console.log(error);
});
}, []);
what I am trying to do is:
construct an URL based on props
initially and whenever the URL changes, fetch some data
Since this is asynchronous and I also want to indicate loading, I use this construct:
const pageUrl = computed(() => `/api/${props.foo}/${props.bar}`)
const state = reactive({
page: null,
error: null,
loading: false
})
watchEffect(async () => {
state.loading = true
try {
const resp = await axios.get(pageUrl.value)
state.page = resp.data
} catch (err) {
state.error = err
console.log(err)
}
state.loading = false
})
// return page, loading, error for the component to use
The problem is that this seems to run in an infinite loop because in the body, I am not only reacting to the pageUrl, but also to state which itself is modified in the function body.
Alternatively, I can use watch(pageUrl, async pageUrl => { ... }), but this seems only to be triggered when pageUrl changes (in my case: I modify the URL because the props are updated via vue-router, but not when I initially visit the URL).
What should I do here, is my idea of signalling the loading state not appropriate here?
From a logical point of view, the page is a computed value, the only reason I use watch here is that it's asynchronous and might yield an error as well.
Thanks to Husam I got aware of the bug, and it seems like changing a piece of state twice introduces this behaviour - in my case setting loading to true and then again to false.
This behaviour is not apparent in Vue3, and a workaround (and in general maybe the much cleaner method) could be to directly use watch instead of watchEffect.
The source code shows different overloads of the funciton, and there is an options argument that is not directly documented in the Vue3 API. There, I found that my call above needs to read like watch(value, async value => { effect }, { immediate: true }).
Good morning everyone.
I have been struggling for several days on an app I'm trying to build for some experience. I have done quite a lot but am now stuck in the edit page as I just cannot grasp the state management side.
So here is my problem.
I have a button in my ProfilePage.vue that if I click on it sends me to the EditInvoice.vue page.
<button #click="onSubmit"><router-link to="/edit-invoice">Edit</router-link></button>
my store.js state:
state: {
invoice: [],
},
Then in my store.js, I have the following in my actions:
actions: {
invoiceCollection({commit}) {
database.collection('invoices')
.get()
.then((querySnapShot) => {
querySnapShot.forEach((doc) => {
const curInvData = doc.data();
commit('invoice', curInvData);
})
})
}
},
This action gets the data I need from firestore and should look like this.
clientDetails: "Adress"
dateCreated: "September 15th 2019"
invoice: Array(2)
invoiceSubTotal: "R 167,50"
invoiceTotal: (...)
itemPaid: (...)
userId: (...)
userName: (...)
I then mutate my state (store.js):
mutations: {
invoice: (state, payload) => state.invoice = payload,
},
and then use a getter (store.js):
getters: {
// Get Invoice data from state
invoice: state => {
return state.invoice
},
},
I then import mapGetters into my component (EditInvoice.vue) ...iterate through my getter's under my computed property with ...mapGetters(['invoice']),
and then use a simple function with a console log and use a lifecycle hook.
created() {
this.currentInvoice();
},
methods: {
...mapActions(['invoiceCollection']),
currentInvoice() {
console.log(this.invoice)
},
I'm very new to programming and would just like to know, why my getters, and everything else always returns an empty Observer
[__ob__: Observer]
length: 0
__ob__: Observer {value: Array(0), dep: Dep, vmCount: 0}
__proto__: Array
on the first few attempts. And then after a few clicks on the edit button in ProfilePage.vue eventually shows the right data
I have been searching for the same issue and have found several cases but none have helped me. This is a simplified version that I stripped. All I want to know is why my state is not persistent. The data is there it's just not showing on the first or second-page load.
Any help will be greatly appreciated.
You are using this.currentInvoice(); in created method, created method run when component is created (not even mounted) so nothing has run, try running this.currentInvoice(); in mounted or beforeUpdate life cycle method.
Also go through this: Vue Js Life Cycle Methods
This is a modified version of the now() implementation from mobx-utils. From my understanding, when the autorun function is triggered, "initial" would be logged, then after 1 second, the value of Date.now(), then Date.now() again and again every second.
function createIntervalTicker(interval) {
let subscriptionHandle
return fromResource(
sink => {
subscriptionHandle = setInterval(
() => sink(Date.now()),
interval
);
},
() => {
clearInterval(subscriptionHandle);
},
'initial'
);
}
autorun(() => {
console.log(createIntervalTicker(1000).current())
})
However, I am getting "initial" logged out every second again and again. The value of Date.now() is never logged.
It seems that when sink(Date.now()) is called, it is only triggering the the autorun function, but not updating the value returned by current().
I would appreciate any advice. Thanks in advance.
mobx#3.1.16
mobx-utils#2.0.2
Your code creates a new observable and passes its value to console.log each time autorun is executed. Hence, you always see initial in the browser console: mobx tracks changes to the initial observable, but console.log receives new observable on each reaction.
Solution: store a reference to the initial observable and reuse it
const ticker = createIntervalTicker(1000);
autorun(() => {
console.log(ticker.current())
})
I am working on a react-native project.
I have Component A that calls Component B.
When screen B is finished doing work, it calls:
NavigationActions.pop({refresh: {workComplete: true}})
And on Screen A, I have the following code:
componentWillReceiveProps(nextProps) {
if (nextProps.workComplete) {
window.alert('work was completed');
}
}
However, the props.workComplete stays set, and I'm not sure how to unset it, so I keep getting an alert when props are changed in this component.
How can I reset that property value?
Not sure what you mean by unset, but this will be called everytime props change and since workComplete is set to true, it will constantly alert you. You can have workComplete as a state value and do something like this:
componentWillReceiveProps(nextProps) {
if (nextProps.workComplete !== this.state.workComplete) {
this.setState({ workComplete: true }, () => window.alert('work was completed'));
}
}