How to set state while the react component is mounting? - asynchronous

My component needs to display the current location. The call to get the geo-location is asynchronous. To display the coords as fast as possible I issue the call to the get the location when the component is created (in the ctor). However, when the async call finishes before the component is mounted I am getting an error:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the component.
Whats the correct way to set the state while the component is mounting?

Related

redux-injectors: Using yield select in saga before reducer and saga are injected

Hello this is my first question. I am trying to set up a project where modules along with the redux and sagas will be injected into the main app, using redux-injectors. In my sagas I want to use yield select, to check if an action has updated the state and then carry on. For example, when I post an image, I want to make sure there were no errors in posting the file and then move on. I use the following function:
export const imageErrors = (state: RootState): IImagesErrorState => state.image.errors
and then in the saga.ts file I use it as such:
if (imagesErrors?.postImageError !== null) {
throw imagesErrors.postImageError
}
this works fine as long as the state.image exists in the root state from the beginning. However, how do I do that when I want to inject this state later on using useInjectReducer and useInjectSaga? I obviously get an error
Property 'image' does not exist on type 'Reducer<CombinedState<{ user: CombinedState<{ auth: IAuthState; errors: IErrorState; }>; }>, AnyAction>'.ts(2339)
So how do we handle selectors of specific pieces of state, since state does not yet include them?
Thank you so much.
Can't talk about the Typescript part of things, but in terms of architecture you've got two options.
One is the obvious - that is to add conditions or ? everywhere to avoid errors from accessing missing properties, but that can get tedious quickly.
The other probably better option is to rethink your state & application chunks. What is this saga that is accessing state that isn't existing yet? Does it need to run before you have such state? If not, let's move the saga to the same chunk as the reducer. In the opposite case, where you need the saga to be running e.g. as part of the runtime chunk, then perhaps the image state should be in the runtime chunk as well.

Can aframe-state-component track state across scenes?

I have an (angular 5 based) a-frame app that has two scenes. I want to keep track of application state such as config parms using the aframe-state-component component. I want to access the cumulative state across both scenes. However, empirically it seems that every time I switch to the other scene all the variables in the state are reset to their initial state.
Also, the fact that you can access the state variables using a statement like:
AFRAME.scenes[0].systems.state.state.score
suggests to me that the state is tied to one scene only (scenes[0] being equal to the current scene).
At first I thought it was an angular issue as the supposed singleton service where I initialized state was being initialized on every scene transfer. But I discovered that linking using the angular router:
this.router.navigate([(evt.target as any).previousSibling.getAttribute('link').href, {}])
Instead of the default:
window.location = this.data.href
Fixed that problem. So it now appears to be an issue with A-frame.
Is the notion that this component can only be used intra-scene and not inter-scene a correct assumption?
Note: I'm also experimenting with angular-redux/store and it seems to have no problem retaining state across scenes, but I'd rather use aframe-state-component as it seems simpler.
You can store in localStorage and restore on load. Something like:
window.addEventListener('beforeunload', () => {
localStorage.setItem('state', JSON.parse(state));
});
initialState: localStorage.getItem('state')
? JSON.parse(localStorage.getItem('state'))
: {
// Initial state...
}
EDIT: Realized you meant for in-app scene changes? You can do something similar. When a scene is unloaded, store it somewhere, then state component checks if it exists?
The state component doesn't seem to store the states, so It would work if you had two scenes on one page:
<a-scene></a-scene>
<a-scene visible="false"></a-scene>
and switch them by setting the visible attribute. I also shows that AFRAME.scenes refer to scenes on the page, since you can log AFRAME.scenes[0] and AFRAME.scenes[1] in the above "example" (or in this fiddle).

ngrx/store subscribe being called multiple times. Best place to unsubscribe?

I have an angular component that contains a NgModal popup.
I am subscribing to the ngrx store in the component.
The component's ngOnDestroy is never called because a new route is never called.
The user add's new Users via the modal popup.
The subscription(select) to the store for the 'user' state never unsubscribes and is being called many times.
What is the best way to handle the unsubscribing from a store.select() when ngOnDestroy() is never called?
The easiest way around this issue is don't subscribe in your component, do it in the template through the async pipe.
From the Angular Docs:
The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks (emphasis added).
Since you mentioned ngOnDestroy is not called, then most likely the component persists and is hidden/shown as needed, in which case you'll just get the single subscribe.
You can also prevent multiple subscribe calls in the template by using the "async as" pattern, as explained by Todd Motto here.. Basically, when encountering the need for multiple async pipes in a template, use *ngIf="data$ | async as data" at a higher element and reference the resolved data below.
Destroying the Subscription inside ngOnDestroy() is useful, if you don't want that subscription to be called in different components
However, if the subscription is getting called multiple time within the same component you can solve this by using RxJs Operators
yourSelectorname$.pipe(skip(1)).pipe(take(1)).subscribe
or
yourSelectorname$.pipe(take(1)).subscribe
It depends according to your need

Does removing all observers also disable keeySynced()

I want to keep a part of my database synced, but only need an actual callback when a certain view is loaded. When the view loads I'm calling:
FIRDatabase.database().reference().child("data").observe(.childAdded...
then when the view exits I want to call
FIRDatabase.database().reference().child("data").removeAllObservers()
Elsewhere in my app I'm calling:
FIRDatabase.database().reference().child("data").keepSynced(true)
I know that keepSynced() simply adds an observer to the ref, so when I call removeAllObservers() will it cancel out the keepSynced(true)?
No, calling keepSynced(true) means that the actual data on the device will be kept in sync with Firebase servers.
This means that when you do eventually add a listener to that location, you will be able to very quickly retrieve the data, because the device has been keeping it in sync for you for a while.
The only way to disable the syncing is to call keepSynced(false).

ngrx, How to have a starting state from an api?

I have my reducer with a starting state of an empty array:
folderReducer(state:Array<Folder> = [], action: Action)
I'd like to populate the starting state, so when I do
store.subscribe(s => ..)
The first item I get comes from the database. I assume the way of doing this is with ngrx/effects, but I'm not sure how.
Your store always has the initial state, that you define in the reducer-function. The initial states main purpose is to ensure that the application is able to start up and not run into any null-pointer-exceptions. And also it sets up your application to start making the first api-calls ect. - so you can think of it as a technical initial state.
If you want to fill your store with api-data on the startup, you would do that on the same way that you add/modify data during any other action - just that the action of "initially loading data" is not triggered by some user-interaction but through:
either when your root-component loads
or as part of a service in the constructor
In case you want to prevent specific components from showing anything until your API-call is done, you would have to adjust the display-components to display or hide data based on your state (e.g. by implementing a flag in your satet initialDataLoaded).
A dynamic initial state is now supported, see: https://github.com/ngrx/platform/blob/master/docs/store/api.md#initial-state-and-ahead-of-time-aot-compilation
Also see: https://github.com/ngrx/platform/issues/51
I would only do this if the database is local, otherwise the request will hold up loading of the application.

Resources