Can't subscribe to results of combineLatest - firebase

My app broke when I updated to RxJS 6. I got most of it working but this one method has me stumped.
Previously we had an array of observables being flatMapped and then used combineLatest like this:
const newObservable = myArrayOfObservables
.flatMap(observables => {
return Observable.combineLatest(observables);
});
And I could subscribe to the newObservable and get an array of the latest outputs from all the others.
Now I'm trying to do something like this:
const mergedList$ = chatList$.pipe(
mergeMap(res => {
return combineLatest(res);
}));
This gives me one of those really long and convoluted
Argument of type '(res: Observable<{ $key: string; }>[]) => OperatorFunction<{}, [{}, Observable<{ $key: string; }>]>' is not assignable to parameter of type '(value: Observable<{ $key: string; }>[], index: number) => ObservableInput<{}>'.
Type 'OperatorFunction<{}, [{}, Observable<{ $key: string; }>]>' is not assignable to type 'ObservableInput<{}>'.
Type 'OperatorFunction<{}, [{}, Observable<{ $key: string; }>]>' is not assignable to type 'Iterable<{}>'.
Property '[Symbol.iterator]' is missing in type 'OperatorFunction<{}, [{}, Observable<{ $key: string; }>]>'.
which quite frankly my dyslexia prevents me from parsing.
If I just return res in the above, and then try
const mergeCombo = combineLatest(mergedList$);
now mergeCombo is not an observable. Instead it's a
OperatorFunction<{}, [{}, Observable<{
$key: string;
}>]>
It could be worth noting that my original observables are being emitted by AngularFireObject.snapshotChanges()

You don't show how you imported combineLatest. It sort of looks like you are importing it as an operator, which has been deprecated.
Make sure you import it like so:
import {combineLatest} from "rxjs";
Then your original code should work just fine:
const mergedList$ = chatList$.pipe(
mergeMap(res => combineLatest(res))
);

Related

Selectors No overload matches this call

Here's my selector.ts
export interface ITestState {
value: number;
}
export interface IReduxState {
test: ITestState;
}
export const selectTest = (state: IReduxState) => state.test;
export const selectTestValue = createSelector(
selectTest,
(state: ITestState) => state.value
);
Ïf i try to use it in app.component.ts
Like so
constructor(private store: Store) {
this.vaschal$ = store.select(selectTestValue);
}
I get the following error
No overload matches this call.
Overload 1 of 9, '(mapFn: (state: object) => number): Observable<number>', gave the following error.
Argument of type 'MemoizedSelector<IReduxState, number, DefaultProjectorFn<number>>' is not assignable to parameter of type '(state: object) => number'.
Types of parameters 'state' and 'state' are incompatible.
Property 'test' is missing in type '{}' but required in type 'IReduxState'.
Overload 2 of 9, '(key: never): Observable<never>', gave the following error.
Argument of type 'MemoizedSelector<IReduxState, number, DefaultProjectorFn<number>>' is not assignable to parameter of type 'never'
angular version 11.2.4
What do i do wrong?
You need to inform the Store of the root store state:
constructor(private store: Store<IReduxState>) {
this.vaschal$ = store.select(selectTestValue);
}

Flow property is missing in mixed passed - despite the type annotation

I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.

Flow errors when dealing with nullable types

I have working on a redux reducer with the following state:
export type WishlistState = {
+deals: ?DealCollection,
+previousWishlist: ?(Deal[]),
+currentWishlist: ?(Deal[]),
+error: ?string
};
export type DealCollection = { [number]: Deal };
export const initialState: WishlistState = {
deals: null,
previousWishlist: null,
currentWishlist: null,
error: null
};
export default function wishlistReducer(
state: WishlistState = initialState,
action: WishlistAction
): WishlistState {
switch (action.type) {
case "GET_DEALS_SUCCESS":
return { ...state, deals: action.deals };
case types.GET_WISHLIST_SUCCESS:
console.log(action);
const currentWishlist: Deal[] = action.wishlistIds.map(
// ATTENTION: THIS LINE HERE
d => state.deals[d]
);
return {
...state,
currentWishlist,
previousWishlist: null,
error: null
};
// ...other cases
default:
return state;
}
}
The line I've flagged with the comment is getting a flow error on the d in the
brackets:
Cannot get `state.deals[d]` because an index signature declaring the expected key/value type is missing in null or undefined.
This is happening because of the type annotation: deals: ?DealCollection, which is made clearer if I change the line to this:
d => state.deals && state.deals[d]
Which moves the error to state.deals; and the idea is that if state.deals is null, then the callback returns null (or undefined), which is not a acceptable return type for a map callback.
I tried this and I really thought it would work:
const currentWishlist: Deal[] = !state.deals
? []
: action.wishlistIds.map(d => state.deals[d]);
It would return something acceptable if there are no deals is null, and never get to the map call. But this puts the error back on the [d] about the index signature.
Is there any way to make Flow happy in this situation?
Flow invalidates type refinements whenever a variable may have been modified. In your case, the thought of checking !state.deals is a good start; however, Flow will invalidate the fact that state.deals must have been a DealCollection because (theoretically) you could be modifying it in your map function. See https://stackoverflow.com/a/43076553/11308639 for more information on Flow type invalidation.
In your case, you can "cache" state.deals when you have refined it as a DealCollection. For example,
type Deal = string; // can be whatever
type DealCollection = { [number]: Deal };
declare var deals: ?DealCollection; // analogous to state.deals
declare var wishlistIds: number[]; // analogous to action.wishlistIds
let currentWishlist: Deal[] = [];
if (deals !== undefined && deals !== null) {
const deals_: DealCollection = deals;
currentWishlist = wishlistIds.map(d => deals_[d]);
}
Try Flow
that way you can access deals_ without Flow invalidating the refinement.

User variable value as type

I try to type redux action creators. I have action types like:
// #flow
export const SET_USER_TOKEN = "SET_USER_TOKEN";
export const SET_TOKEN_IN_COOKIES = "SET_TOKEN_IN_COOKIES";
Now I want to create a type of action creator:
import * as actions from "./actions";
type SetUserTokenAction = {
type: actions.SET_USER_TOKEN,
token: string
};
The problem is with type property, I can not do it like above. Do I have to use literal string?
I know it is possible to do something similar for a whole object:
type ActionTypes = $Keys<typeof user>;
I would like to do the same for the value of a simple string variable.
You can use typeof oprator:
type SetUserTokenAction = {
type: typeof actions.SET_USER_TOKEN,
token: string
};
The only caveat is to type the const as well (otherwise its type resolved as string):
export const SET_USER_TOKEN: 'SET_USER_TOKEN' = 'SET_USER_TOKEN';

How do I statically type a response from `window.fetch` with Flow?

I'm window.fetch'ing a response from an JSON API, and I'd like to type check my access to the response. For example:
type Result = {|+text: string, +metadata: {...}|};
type ApiResponse = Response & {|
json: () => Result,
text: null,
|};
const getResult = (): Promise<ApiResponse> => fetch(url);
// access to getResult().then(r => r.json()) is type checked against Result
But Flow fails to type check with:
Error: src/data/fetcher.js:18
v-
18: export type ApiResponse = Response & {|
19: json: () => Promise<Result>,
20:
...:
23: |};
-^ exact type: object type. Inexact type is incompatible with exact type
987: declare function fetch(input: RequestInfo, init?: RequestOptions): Promise<Response>;
^^^^^^^^ Response. See lib: /private/tmp/flow/flowlib_211b7075/bom.js:987
Which I guess makes sense because it can't reconcile fetch's return type of Promise<Response> with getResult's return type of Promise<ApiResponse>.
How can I constrain that the thing getResult is returning is a Promise?
How can I constrain that the thing getResult is returning is a Promise?
Type it as a Promise<Response>
The type for Response.json() already resolves to any, so you can assert it is whatever type you want:
declare class Response {
...
json(): Promise<any>;
...
}
The next question you might have is "How do I let Flow know the type returned upon calling json()?"
Type that as whatever you're expecting. Something like this:
fetch(url)
.then(res => res.json())
.then((obj: Result) => {
// Access your obj here
// (Preferably with some level of checking to make sure
// the API returned a valid object)
})

Resources