Not understanding behavior of MobX-utils fromResource - mobx

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())
})

Related

React-Native - useEffect causes infinite loop

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);
});
}, []);

React native setState using object

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"])
})

Vue.js Vuex State Never Updates With push()

I have a Vuex store that manages an array (state.all), and I have a button that calls a Vuex action which performs an HTTP call and then appends the the data in the response to state.all by way of a mutation. However, the state never gets updated and the components never update.
In order prove that I was not crazy, I used two alert()s inside of the mutation to make sure I knew where I stood in the code. The alert()s were always fired with proper values.
Here is the truncated Vuex store (this is a module):
const state = {
all: []
}
// actions
const actions = {
...
runner ({ commit, rootState }, { did, tn }) {
HTTP.post(url, payload)
.then(function (response) {
commit('setNewConversations', response.data)
})
})
}
}
const mutations = {
...
setNewConversations(state, new_conv) {
for (let new_c_i in new_conv) {
let new_c = new_conv[new_c_i]
alert(new_c) // I always see this, and it has the correct value
if (!(new_c in state.all)) {
alert('I ALWAYS SEE THIS!') // testing
state.all.push(new_c)
}
}
}
...
}
When I go to test this, I see my two alert()s, the first with the value I expect and the second with "I ALWAYS SEE THIS!" but nothing happens to my v-for component and the state never updates, despite the state.all.push().
What is the next step to troubleshooting this issue? There are no errors in the JS console, and I cannot figure out any reason the state would not be updated.
Thank you!
One possible solution is instead of pushing to the current state value, store the previous value of state.all in a new array and push the new changes to that new array.
Once done, assign that new array to state.all like the following below.
setNewConversations(state, new_conv) {
const prevState = [...state.all];
for (let new_c_i in new_conv) {
let new_c = new_conv[new_c_i]
if (!(new_c in prevState)) {
prevState.push(new_c);
}
}
state.all = prevState;
}
Given that you said that removing the alert makes it work makes me wonder if you are just observing the value in the wrong place. I can't be sure from what you've given.
Remember that Javascript is single-threaded, and your mutation has to complete before any other Vue-injected reactivity code can run.
If you really wanted the value to be shown before the mutation is complete, you could probably call Vue.nextTick(() => alert(...)), but the better answer is to check for the updates somewhere else, such as in a computed that calls the getter for the state.all array.
(By the way, I find that using either console.log(...) or the vue-dev-tools is much faster than alert() for arbitrary debugging.)

Redux Observables: General way to return non-observable in mergeMap?

observable.
In my epic, I just want to call a 3rd party library for scheduling a push notification on iOS (I'm using react native):
import 'rxjs';
import { Observable } from 'rxjs';
import PushNotification from 'react-native-push-notification';
import * as calendarActions from '../ducks/calendar';
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.mergeMap(action => {
return new Observable(observer => {
const meetupId = action.payload;
PushNotification.cancelLocalNotifications({ id: meetupId });
observer.next(meetupId);
});
})
.map(action => calendarActions.rsvpAdded(action.payload));
};
This works fine, but I was wondering if this is the most common approach to return an Observable and inside it just to call observer.next()?
If you need to create an Observable that wraps non-Observable code and you want to observe the results of that code--that's the crucial part.
If you don't care about whether the side effect produces anything, errors, or if it completes asynchronously or not, then wrapping it in a custom Observable isn't neccesary. You could just use the .do() operator instead.
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.do(action => PushNotification.cancelLocalNotifications({ id: action.payload }))
.map(action => calendarActions.rsvpAdded(action.payload));
};
There's one missing thing I want to point out in your code, however. You never call observer.complete() which means you're accidentally leaking the subscription to that custom Observable. Every time a new CANCEL_RSVP comes in another one will be created and subscribed to (mergeMap) with the previous sticking around even though it has no work left to be done.
Remember to always call observer.complete() when you're done, unless of course your Observable intentionally never completes. 😄
(also, it emits observer.next(meetupId) but then later .map(action => but that might just be a typo in this question, not your apps code)

Debouncing a Vue Component method with Lodash

I am attempting to use Lodash's debounce on a Vue 2 method in order to only run once a user has stopped typing in an input field, but I am getting unexpected results:
INPUT FIELD
<input type="text" v-model='filter.user' placeholder="search" #keyup='dTest'>
METHOD
dTest() {
const d = _.debounce(() => {
console.log('hi');
}, 2000);
d();
}
However, 'hi' is being logged to the console on every keypress, with a two second delay.
thanks
Change dTest to:
dTest = _.debounce(() => {
console.log('hi');
}, 2000);
With your dTest, you are creating a new debounce function every time dTest is run. You are meant to create this function only once, like you see above, then call that function every time.