I'm trying to render a page with some details I get from a api call.
useEffect(() =>{
getCards();
}, [])
const [userCards, setCards] = useState([])
const getCards = async (event) => {
let token = localStorage.getItem("user");
await api
.get("/fetch-card-balance",
{headers:{"token":`${token}`}})
.then((response) => {
console.log(response);
if (response.data.success === false) {
toast.error(response.data.message);
setCards(false);
} else if (response.data.success === true) {
console.log(response.data.payload)
setCards(response.data.payload)
}
})
.catch((err) => {
toast.error(err.response.data.message);
});
};
console.log(userCards)
Here userCards is logged as
[
{
balance: 0.00,
cifNumber: "0001111222",
createdAt: "2021-08-03T12:19:51.000Z",
first6: "123456",
id: 1234,
last4: "7890"
},
{
balance: 20.00,
cifNumber: "0002222333",
createdAt: "2021-07-03T12:19:51.000Z",
first6: "234567",
id: 2345,
last4: "8901"
}
]
Then I try to use forEach to filter the properties I need
const cardDetails = []
userCards.forEach(option => cardDetails.push(
{
cardNumber: `${option.first6}******${option.last4}`,
balance: `${option.balance}`
}
))
But when I run
console.log(cardDetails[0].balance)
I get "Uncaught TypeError: Cannot read property 'balance' of undefined". I've gone over it several times and the only conclusion I have is that I'm missing something that may not be so obvious. Could someone help point out what it is.
Using cardDetails[0].balance will only work when there is at least one element in cardDetails. Otherwise getting the first element in the array yields undefined, causing your error message. Since you only fill the array after the API request returns, at least your first render will be done with an empty array.
An easy way to handle this would be checking for if (cardDetails.length > 0) first.
Try this out
const cardDetails = userCards.map(function(option) { return {cardNumber: ${option.first6}******${option.last4}, balance: ${option.balance}}});
Related
New react developer here, here i have two API's, first one gives an object :
{ id: "98s7faf", isAdmin: true, name: "james"}
second one gives an array of objects :
[
{ billingName: "trump", driverName: "james" },
{ billingName: "putin", driverName: "alex" },
{ billingName: "kalle", driverName: "james" },
{ billingName: "sussu", driverName: "trump" },
{ billingName: "vladimir", driverName: "james" },
]
my question is, when user goes to the page, the page should automatically check both API'S, from first api name and from second api driverName, and if those two have same value then take that specific object from an array and pass it to these:
setOrders(res);
setRenderedData(res);
so at the moment there are three objects (those which have value of james) inside an array which matches name from first api, so it should pass those, and the rest which have different value it should not let them pass. here is my code, what have i done wrong ? instead of some(({ driverName }) i need some kind of filter ?
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
return api.caApi.apiCaGet(request).then((res?: CaDto[])
=> {
if (res.some(({ driverName }) => driverName === res1?.name)) {
setOrders(res);
setRenderedData(res);
console.log(res);
}
});
});
}, [api.caApi, api.userApi]);
you need to filter your response to extract the right objects given your condition.
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
return api.caApi.apiCaGet(request).then((res?: CaDto[])
=> {
const filteredData = res?.filter(({ driverName }) => driverName === res1?.name);
if(filteredData?.length) {
setOrders(filteredData);
setRenderedData(filteredData);
console.log(filteredData);
}
});
});
}, [api.caApi, api.userApi]);
note: having 2 states that holds the same values (orders, renderedData) is not a good practice, you might consider to refactor your logic.
I am beating my head against a wall. I have updated to Apollo 3, and cannot figure out how to migrate an updateQuery to a typePolicy. I am doing basic continuation based pagination, and this is how I used to merged the results of fetchMore:
await fetchMore({
query: MessagesByThreadIDQuery,
variables: {
threadId: threadId,
limit: Configuration.MessagePageSize,
continuation: token
},
updateQuery: (prev, curr) => {
// Extract our updated message page.
const last = prev.messagesByThreadId.messages ?? []
const next = curr.fetchMoreResult?.messagesByThreadId.messages ?? []
return {
messagesByThreadId: {
__typename: 'MessagesContinuation',
messages: [...last, ...next],
continuation: curr.fetchMoreResult?.messagesByThreadId.continuation
}
}
}
I have made an attempt to write the merge typePolicy myself, but it just continually loads and throws errors about duplicate identifiers in the Apollo cache. Here is what my typePolicy looks like for my query.
typePolicies: {
Query: {
fields: {
messagesByThreadId: {
keyArgs: false,
merge: (existing, incoming, args): IMessagesContinuation => {
const typedExisting: IMessagesContinuation | undefined = existing
const typedIncoming: IMessagesContinuation | undefined = incoming
const existingMessages = (typedExisting?.messages ?? [])
const incomingMessages = (typedIncoming?.messages ?? [])
const result = existing ? {
__typename: 'MessageContinuation',
messages: [...existingMessages, ...incomingMessages],
continuation: typedIncoming?.continuation
} : incoming
return result
}
}
}
}
}
So I was able to solve my use-case. It seems way harder than it really needs to be. I essentially have to attempt to locate existing items matching the incoming and overwrite them, as well as add any new items that don't yet exist in the cache.
I also have to only apply this logic if a continuation token was provided, because if it's null or undefined, I should just use the incoming value because that indicates that we are doing an initial load.
My document is shaped like this:
{
"items": [{ id: string, ...others }],
"continuation": "some_token_value"
}
I created a generic type policy that I can use for all my documents that have a similar shape. It allows me to specify the name of the items property, what the key args are that I want to cache on, and the name of the graphql type.
export function ContinuationPolicy(keyArgs: Array<string>, itemPropertyKey: string, typeName: string) {
return {
keyArgs,
merge(existing: any, incoming: any, args: any) {
if (!!existing && !!args.args?.continuation) {
const existingItems = (existing ? existing[itemPropertyKey] : [])
const incomingItems = (incoming ? incoming[itemPropertyKey] : [])
let items: Array<any> = [...existingItems]
for (let i = 0; i < incomingItems.length; i++) {
const current = incomingItems[i] as any
const found = items.findIndex(m => m.__ref === current.__ref)
if (found > -1) {
items[found] === current
} else {
items = [...items, current]
}
}
// This new data is a continuation of the last data.
return {
__typename: typeName,
[itemPropertyKey]: items,
continuation: incoming.continuation
}
} else {
// When we have no existing data in the cache, we'll just use the incoming data.
return incoming
}
}
}
}
I'm working on a project that is a basic message app. basically i have created an array of objects that allows me to see pre built messages. Then i should be able to click a clear all button and clear all of the messages that are being displayed by looping through the array of objects. this is what i have so far in my messageData.js
const messages = [
{
id: 'message1',
message: 'Hello everyone! Welcome to hell',
userId: 'user1',
},
{
id: 'message2',
message: 'Yall are weirdos!',
userId: 'user3',
},
{
id: 'message3',
message: 'Hey! I think everyone is awesome!',
userId: 'user2',
},
{
id: 'message4',
message: 'Thanks for saying that my friend.',
userId: 'user4',
},
{
id: 'message5',
message: 'Hey buddy, what is up?',
userId: 'user4',
},
];
const getMessages = () => messages;
and what i want to do is basically on click allow the messages key value to be changed to an empty string onclick so that i get rid of the displayed messages without getting rid of the object so that i can later push new messages into these key values.
I started to write this but i seem to be missing something..
const clearBtnFunction = () => {
messages.splice(1, '');
};
i'll be calling the event listener on my main.js file so i'm not super worried about that part yet. I just want to know the proper syntax for replacing the key value in the array if thats possible.
Here is what i opted for. I placed this function, not in the messagesData.js but in the messages.js where i'm building the domstring
const clearBtnFunction = (e) => {
e.preventDefault();
const messages = message.getMessages();
messages.splice(0, messages.length);
messageBuilder(messages);
};
const clearBtnFunction = () => {
messages.foreach( ( message ) => {
message.message = "";
});
};
or with a for loop
const clearBtnFunction = () => {
for( let i =0; i < messages.length; i++) {
messages[i].message = "";
}
};
Probably a silly issue, but why is the Array.find method not working as expected when working in this case? I'm trying to query a specific comment, which involves fetching the post document that has a comments property from the DB. It is from this comments array that I'd like to extract said comment object. For whatever reason, the code below doesn't work. Why?
Below are the code snippets
// Post document from which the comments are extracted
const post = await Post.findById(postId).populate({
path: "comments",
select: "addedBy id"
});
// Resulting post.comments array
[
{ "id": "5d9b137ff542a30f2c135556", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0ba2f28afc5c3013d4df", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0c26f28afc5c3013d4e0", "addedBy": "5b8528131719dc141cf95c99" }
];
// For instance if commentId is '5d9b137ff542a30f2c135556'
// the resulting comment object should be {"id":"5d9b137ff542a30f2c135556","addedBy":"5b8528131719dc141cf95c99"}
// However, commentToDelete is undefined
const commentId = "5d9b137ff542a30f2c135556";
const commentToDelete = comments.find(comment => comment["id"] === commentId);
Edit: Here's the full deleteComment controller code
async function deleteComment(req, res, userId, postId, commentId) {
const post = await Post.findById(postId).populate({
path: 'comments',
select: 'addedBy id',
});
const commentToDelete = post.comments.find(
comment => comment['id'] === commentId
);
if (commentToDelete.addedBy !== userId) {
return res
.status(403)
.json({ message: 'You are not allowed to delete this comment' });
}
await Comment.findByIdAndDelete(commentId);
const updatedPost = await Post.findByIdAndUpdate(
post.id,
{ $pull: { comments: { id: commentId } } },
{ new: true, safe: true, upsert: true }
).populate(populateFields);
return res.status(200).json({ updatedPost });
}
comment => comment['id'] === commentId
Your comment subdocument comes from MongoDB/Mongoose, so comment['id'] will likely be of type ObjectID, which is never equal a string. Explicitly call the toString() function (or use some other approach for transforming to a string) before comparing:
comment => comment['id'].toString() === commentId
works fine in the below snippet, copied from your post!
I am assuming it is posts.comments in your case and not comments.find? Check for typos
const comments = [
{ "id": "5d9b137ff542a30f2c135556", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0ba2f28afc5c3013d4df", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0c26f28afc5c3013d4e0", "addedBy": "5b8528131719dc141cf95c99" }
];
// For instance if commentId is '5d9b137ff542a30f2c135556'
// the resulting comment object should be {"id":"5d9b137ff542a30f2c135556","addedBy":"5b8528131719dc141cf95c99"}
const commentId = "5d9b137ff542a30f2c135556";
// However, commentToDelete is undefined
const commentToDelete = comments.find(comment => comment["id"] === commentId);
console.log(commentToDelete);
you can use this :
const result = comments.find(
({ id }) => id === commentId,
);
console.log(result)
// should return { id: '5d9b137ff542a30f2c135556', addedBy: '5b8528131719dc141cf95c99' }
I have two values I need to match as you can see in the picture below:
I tried something like this:
const index = state.locks.users.findIndex(
stateUser => stateUser._id === action.payload.customerPayload.accessid
But I’m getting the error:
findIndex of undefined.
And I guess that’s because of locks being an array.
But I’m really uncertain how to fix this issue. Should I have multiple findIndexes? One for the lock and one for to match the users?
Thanks for reading my post. And I appreciate all the help I can get.
The code snippet should be
let itemIndex = -1;
state.locks.map((lock) => {
lock.users.findIndex(...)
});
Assuming state is an object containing locks array.
My suggestion to you is do a double for loop (as you've already figured) to get the user object that you need.
Consider the following snippet (adjust to your data structure):
let state = {
locks: [
{
users: [
{ _id: '123' },
{ _id: '456' }
]
},
{
users: [
{ _id: '678' },
{ _id: '789' }
]
},
]
};
function getUserObjByID(stateObj, userID) {
for (let usersObject of state.locks) {
for (let user of usersObject.users) {
if (user._id === userID) {
return user;
}
}
}
}
let myObj = getUserObjByID(state, '678');
console.log(myObj);
So it works now. What I had to do with my reducer was this:
case 'REMOVE_USER':
return {
...state,
locks: state.locks.map(lock => {
return {
...lock,
users: lock.users
? lock.users.filter(
user => user._id != action.payload.customerPayload.accessid
)
: []
}
})
}