Typescript: handle data from service - javascript

I would like to display users' data, but I have a problem with displaying the correct profile pictures. If a user does not have a profile picture, I get "undefined" in the console. If one user has a profile picture, then the same picture will be displayed for all the users. I need help finding the error in my code.
export interface UserData {
id: number,
name: string
}
export interface UserWithImage extends UserData{
image?: string
}
export interface UserProfileImage {
id: number,
url: string
}
After I get the necessary data from the services, I try to push the profile image into the userData.
user-data.ts
userData: UserWithImage[];
userProfiles: UserProfileImage[];
userProfileImage: UserProfileImage[];
getUserData() {
this.userData = this.userService.getData();
this.userProfiles = await this.imagesService.getProfilePicture(this.userData?.map(u => u.id));
this.userProfileImage = this.userProfiles.filter(u => u.url);
this.userData?.forEach((data, i) => {
data.image = this.userProfileImage[i].url;
});
}
images.service.ts
public async getProfilePicture(ids: number[]): Promise<UserProfileImage[]> {
const toLoad = ids.filter(id => !this.userProfileImages.find(up => up.id === id)).map(u => u);
if (toLoad || toLoad.length) {
const loaded = (await firstValueFrom(this.httpClient.post<UserProfile[]>
(this.imgService.getServiceUrl(customersScope, `${basePath}settings/users/profil`), JSON.stringify(toLoad), {headers}))).map(sp => {
return {
id: sp.userId,
url: sp.profilepicId ? this.imgService.getServiceUrl(customersScope,
`${basePath}web/download/profilepic/${sp.profilepicId}/users/${sp.userId}`, true) : ''
} as UserProfileImage
});
this.userProfileImages = [...loaded, ...this.userProfileImages];
}
return this.userProfileImages;
}
user-data.html
<div ngFor="data of userData">
<etc-profil [name]="data.name" [image]="data.image"></etc-profil>
</div>

this.userData = this.userService.getData();
Is this an async function (i.e. are you missing an await)?
this.userProfiles = await this.imagesService.getProfilePicture(this.userData?.map(u => u.id));
This line would fail is this.userData is a promise. this.userProfiles would be undefined due to the use of optional chaining (?.)
this.userProfileImage = this.userProfiles.filter(u => u.url);
This line appears to do nothing, the filter predicate is saying that anything with a url property that is not null or undefined is included, but the interface says that url is non-optional and doesn't support null or undefined.
this.userData?.forEach((data, i) => {
data.image = this.userProfileImage[i].url;
});
Again, if this.userData is a promise, this will do nothing due to the optional chaining.
If it does run, its assumed that there is a one-to-one relationship between users and profile images (index count and order must be the same).
I didn't consider the implementation of getProfilePicture because I think these issues need resolving first.

Related

Why i am not getting the old data together with new data on implementing "react-infinite-scroll-component" with redux toolkit?

I am having a problem and I couldn't move ahead.
I have some data (in form of an array of objects):
[{...}, {...}, {...}, {...}, { id : 5, name : 'Elon'}, {...}, ...];
I am using json-server for fetching the above data.
My file for sending GET request
named agent.ts (I am using typescript)
axios.defaults.baseURL = 'http://localhost:3000/';
const responseBody = ( Response : AxiosResponse ) => Response.data;
const request = {
get : (url : string, params : URLSearchParams) => axios.get(url, {params}).then(responseBody); // as simple wrapper for request along with *'.then'*
}
// request to get list of above data array
const Data = {
list : (params : URLSearchParams) => if(params) { request.get('products', params);
}};
export default Data; // that's it
I have some type files for this data above (since I am using react - typescript)
data.ts
export interface Data {
id : number;
name : string;
}
some parameters I am going to send back to server.
dataParams.ts
export interface DataParams {
_page : number; // just a default json-server parameter ( you know it right?)
_limit : number; // just a default json-server parameter ( you know it right?)
}
let's move everything to redux store
interface DataState {
loadedData : boolean;
dataParams : DataParams;
status : string; // just for checking status on different promises (pending, full, rej..)
}
export const dataAdapter = createEntityAdapter<Data>();
// function for appending params to URLSearchParams (which is an interface in typescript)
function ParamAppender (p : DataParams ) {
const newParam = new URLSearchParams;
if(p._limit) newParams.append('_limit', p._limit); // key - value pair concept
if(p._page) newParams.append('_page', p._limit); // key - value pair concept
return newParams;
}
// what are initial params - here they are
function InitialParams () {
return {
_page : 1,
_limit : 10
}};
export const fetchDataAsync = createAsyncThunk<Data[], void, {state : RootState}>(
'data/fetchDataAsync',
async ( data, thunkAPI ) => {
const param = ParamAppender(thunkAPI.getState().data.dataParams); // i think you understood what i did here, I just called dataParams (via RootState), (its working fine)
try {
return await agent.Data.list(param); // putting param here and sending to API
} catch (error) { return thunkAPI.rejectWithValue({error : error.data})};
});
// now slice part
export const dataSlice = createSlice({
name : 'data',
initialState : dataAdapter.getInitialState<DataState>({
loadedData : false,
dataParams : null,
status = 'idle' // everything is fine here
}),
reducers : {
// 1st reducer
setDataParams : (state, action) => {
state.loadedData = false; // fine here, no problems : )
state.DataParams = {...state.DataParams, ...action.payload};
},
// 2nd reducer - reset
resetDataParams : (state) => {
state.dataParams = InitParams(); (// since this function is a resetter , isn't it?)
}
},
extraReducers : (builder => {
// when fulfilled
builder.addCase(fetchDataAsync.fulfilled, (state, action) => {
dataAdapter.setAll(state, action); // setting data into entity adapter (named as *'dataAdapter'*)
state.loadedData = true;
}),
// when pending
builder.addCase(fetchDataAsync.pending, (state) => {
state.status = 'pending';
}),
// when rejected
builder.addCase(fetchDataAsync.rejected, (state) => {
state.status = 'idle';
}),
})
});
// exporting everything (actions + entity selector (for data))
export const { setDataParams, resetDataParams } = dataSlice.actions;
export const dataSelector = dataAdapter.getSelectors((state : RootState ) => state.data);
A very basic redux slice, above, and it is working, no issue up there
Now, here's the final picture, which ruined my day, and night if I don't get a solution to it..alas
here's my end component - named DataComponent.tsx
Alright, i am not putting here wrapper for <InfiniteScroll></InfiniteScroll> , as it would be so confusing. For now, I am just putting a button to test the functionality.
export default function DataComponent () {
// just a local state for loading new dataset
const [loadedNew, setLoadedNew] = useState(false);
// lets bring the states from redux
const { loadedData, dataParams } = useAppSelector(state => state.data);
const dispatch = useAppDispatch();
// brining data over here
const Data = useAppSelector(dataSelector.selectAll); // selecting All entities (no problem here ...)
// a call back - (its not creating any problem, just please have a look only)
const fetchDataHere = useCallback (async() => {
if (!loadedData) await dispatch(fetchDataAsync()); // simple call
}, [ dispatch, loadedData]); // dependencies change accordingly
// effect thus produced
useEffect(() => {
fetchDataHere();
},
[ fetchDataHere ]);
// call new data function
function CallNewData () {
setLoadedNew(true); // loading on
dispatch(setDataParams({ _page : dataParams._page + 1 })); // just calling new page and sending to server , it is working fine
setLoadedNew(false); // loading off
};
return (
<Fragment>
<List>
{Data.map((data) => (
<ListItem key = {data.id}>{data.name}</ListItem> // as simple mapping just a 'name' property
))}
</List>
<Button onClick = {CallNewData} >{'Load New Data'}</Button>
</Fragment>
)}
My problem (or actually not a problem, but I don't know how to get a workaround with this) is:
Pagination is working fine, when I click button, it load page 2, then on clicking again, it loads page 3, and so on...
But, I am not getting the older data together with the new page (ie, I want page 1's data together with page 2's data, when I am at page 2).
on Making a new local-state here as
const [newData, setNewData] = useState<Data[]>(Data);
and then using newData.map(...)
nothing is showing into browser at all.
I am really stuck, I want to achieve the above mentioned functionality.
How and what should I do to get my old data together with the new data into my CallNewData() function above.
I know I have to use spread ... operator in an array, but how to do this? I really want this functionality to be working
If this functionality worked with onClick of a button,
I am pretty sure, it is going to work with any 3rd party infinite scroll libraries.

Resolve two GraphQL schema fields using one endpoint

There's a situation where there are two possible types to fill data property. I have made a union type for that (ComponentItem) to determine which field needs to be returned. The first schema (ComponentItem1) should just be a hardcoded list but the second one (ComponentItem2) is more dynamic where it gets a searchTerm from query and it actually calls an endpoint to fill the list and also has hasNextPage property.
Here are the schemas I made:
type Component {
id
title
data: ComponentItem
}
union ComponentItem = ComponentItem1 | ComponentItem2
type ComponentItem1 {
list: [List!]!
}
type ComponentItem2 {
hasNextPage: Boolean;
list: [List!]!
}
In the resolver I'm resolving the union type in order to generate proper __typename:
const resolver: {
ComponentItem: {
__resolveType(object) {
if(object.searchTerm){
return "ComponentItem2"
}
return "ComponentItem1"
},
},
}
What I'm currently doing is to resolve the list and hasNextPage individually but in this scenario I'm sending the request to the same endpoint twice.
const resolver = {
...resolver,
ComponentItem2: {
list: async (root, __, context) => {
const result = await fetch('search-endpoint')
return result?.items || []
}
hasNextPage: async (root, __, context) => {
const result = await fetch('search-endpoint')
return result?.hasNextPage || false
}
}
}
My question is how it is possible to share that result in another field resolver (Other than using "context"). Or if there's any better way to handle this situation let me know.

TypeScript - Send optional field on POST only when there is value

I am using Formik, in my React application. I have a simple form with 3 fields. I am doing 2 operations with that form. Add/Edit Resources.
My Problem is that one field is optional. Meaning I should never send it, if its value is null. Currently, I send an empty string which is wrong.
I am using TS-React-Formik, and here is my code for the handleSubmit method:
interface IValues extends FormikValues {
name: string;
owner?: string;
groups: string[];
}
interface CreateAndEditProps {
doSubmit(service: object, values: object): AxiosResponse<string>;
onSave(values: IValues): void;
}
handleSubmit = (values: FormikValues, formikActions:FormikActions<IValues>) => {
const { doSubmit, onSave, isEditMode } = this.props;
const { setSubmitting } = formikActions;
const payload: IValues = {
name: values.name,
groups: values.groups,
owner: values.owner
};
const submitAction = isEditMode ? update : create;
return doSubmit(submitAction, payload)
.then(() => {
setSubmitting(false);
onSave(payload);
})
.catch(() => {
setSubmitting(false);
});
};
I thought a simple if statement would work, and while it does, I do not like it at all. Let me give you an example of why. If I add 2 more optional fields, as I am about to do, in a similar form, I do not want to do several if statements to achieve that.
If you could think of a more elegant and DRY way of doing it, It would be amazing. Thank you for your time.
Look at the removeEmptyKeys() below.
It takes in an Object and removes the keys that have empty string.It mutates the original Object, please change it accordingly if you expect a diff behaviour.
In your code after defining payload, I would simply call this method , removeEmptyKeys(payload)
Also it will resolve your if else problem.
removeEmptyKeys = (item)=>{
Object.keys(item).map((key)=>{
if(payload[key]===""){
delete payload[key]}
})}
var payload = {
one : "one",
two : "",
three : "three"
}
removeEmptyKeys(payload)
Please mark it as resolved if you find this useful.
For your code :
const removeEmptyKeys = (values: IValues): any => {
Object.keys(values).map((key) => {
if (payload && payload[key] === "")
{ delete payload[key] } })
return values;
}

Get data from firestore and put into array in Ionic

I am creating an app in angular, and one task is, I need read data from Firestore and save into an array, my code snippet is as below:
public getListingData(): Observable < RequestListingModel > {
console.log("getting listing...");
this.firestore.collection('Requests').snapshotChanges().subscribe(
requests => {
this._listRequestItems = requests.map(a => {
const data = a.payload.doc.data() as RequestItemModel;
data.requestId = a.payload.doc.id;
console.log("doc found - " + data.requestId);
return data;
})
});
const requestListingModel = {
items: this._listRequestItems
}
as RequestListingModel;
return of(requestListingModel);
}
And my code of RequestListingModel is as below:
import * as dayjs from 'dayjs';
export class RequestItemModel {
name: string;
address: string;
category: string;
requestId: string;
postBy: string;
description: string;
// Default mock value
// expirationDate = '12/01/2018';
expirationDate: string = dayjs().add(5, 'day').format('MM/DD/YYYY HH:mm:ss') as string;
}
export class RequestListingModel {
items: Array<RequestItemModel> = [];
constructor(readonly isShell: boolean) { }
}
And it's not working as I always get empty return when call the function getListingData(), but the console can print out the each requestId successfully, I think something wrong with the way I store into array, please help, thanks!
Your problem is happening because things are happing asynchronously:
So return of(requestListingModel); gets called before this.firestore.collection('Requests').snapshotChanges().subscribe(...) populates the array, thus returning an observable of an empty array.
Since it looks like you want to return an observable, don't subscribe() inside your method. Just do a map() on the snapshotChanges observable to transform the output, and let the consuming code subscribe to your getListingData() method.
public getListingData(): Observable < RequestListingModel > {
console.log("getting listing when someone subscribes...");
return this.firestore.collection('Requests').snapshotChanges().pipe(
map(requests => {
let requestListingModel = {
items: []
} as RequestListingModel;
requests.map(a => {
let data = a.payload.doc.data() as RequestItemModel;
data.requestId = a.payload.doc.id;
console.log("doc found - " + data.requestId);
requestListingModel.items.push(data);
});
return requestListingModel;
})
);
}
Note: still... can't test this at the moment, so it may not be 100% correct :-\
But hopefully points you in the right direction. :-)

Flow Types with Promises (Fetch's)

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
getCurrentJobAPI = (): {} => {
const url: string = `dummy_url&job_id=${this.props.currentJob}`;
return fetch(url, {credentials: 'include'})
.then((response) => {
return response.json();
})
.then((json: CurrentJob) => {
console.log(json);
const location = json.inventoryJob.location;
const ref_note = json.inventoryJob.note;
const id = json.inventoryJob.id;
const models = json.inventoryJobDetails.map((j) => {
return Object.assign({}, {
code: j.code,
qty: j.qty
})
});
this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
return json
})
.then((json: CurrentJob) => {
const barcodes = json.inventoryJob.history;
if (barcodes.length > 0) {
this.setState({apiBarcodes: barcodes})
}
this.calculateRows();
this.insertApiBarcodes();
this.setState({ initialLoad: true });
})
};
UPDATE:
Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and comments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.
[I have condensed my .then() statements per loganfsmyth's recommondation.]
Here are the type definitions for CurrentJob:
type Job = {
user_id: number,
status: 'open' | 'closed',
location: 'string',
history: {[number]: string}[],
note: string,
} & CommonCurrentJob;
type JobDetails = {
iaj_id: number,
code: number,
} & CommonCurrentJob;
type CommonCurrentJob = {
id: number,
qty: number,
qty_changed: number,
created_at: string,
updated_at: string
}
So first off, a disclaimer, I am a TypeScript user but I find that this question is actually applicable to both languages and has the same answer.
I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
TL;DR: Promise<void> (see note). As you suspect, this is in fact the return type of the last top-level .then in the promise chain.
Now lets dig a bit deeper
Here is your example, reworked very slightly to leverage type inference instead of annotating callback parameters that are declared as any by their receivers.
As an aside, these callback parameter annotations amount to unsafe implicit casts, or type assertions as we call them in TypeScript, and they lie about the shape of the code. They look like this
declare function takesFn(fn: (args: any) => any): void;
So I have minimized these since they form a subtle trap
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI: () => Promise<void> = () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
return fetch(url, {credentials: 'include'})
.then(response => {
return (response : {json(): any}).json();
}) // --> Promise<any>
.then(json => {
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails
.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
return currentJob;
}) // --> Promise<CurrentJob>
.then(currentJob => {
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
}); // --> Promise<void>
};
}
So I am making assertions about the promises in each then call above but those assertions are all validated by type inference with the exception of the initial type cast on the response value.
As further evidence, if we remove the type declaration from the getCurrentJobAPI property of A, flow will infer that its type is in fact Promise<void>.
Bonus: simplifying with async/await. I've used several ESNext features above to shorten the code and make it a bit more pleasant, but we can leverage a specific feature, async/await to make it easier to understand control flow and types in Promise based code.
Consider this revision.
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI = async () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
const response = await fetch(url, {credentials: 'include'});
const json = await response.json();
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
};
}
Clearly, this is a void function. It has no return statements. However, as an async function, it inherently returns a Promise, just as it did when written as an explicit Promise chain.
Note: void is a construct that has been found useful in Flow and TypeScript to represent the semantic intent of function that do not return values but in reality such functions actually return undefined because, well, this is JavaScript. Flow does not seem to recognize undefined as a type, but under TypeScript, the function could equally be annotated as returning Promise<undefined>. Irregardless, Promise<void> is preferable thanks to the clarity of intent it provides.
Remarks: I worked through this using a combination of https://flow.org/try and the flow binary for Windows. The experience on Windows is really terrible and hopefully it will improve.
When chaining then's, the result will always be a promise.
When calling then, the return value is another promise, otherwise chaining then's wouldn't have been possible.
You can see that easily by using console.log() surrounding the entire chain.

Categories

Resources