React state delay and missing items when called frequently - javascript

I have a graphql subscription that my front end subscribes to and it updates every 15 seconds and passes about 30 different objects (not as an array, but individual).
I'm catching the data and trying to set state by pushing it into an array, but the output is unexpected, many items are missing after the first sets of data comes in.
ie i have a useEffect that is listening for updates on transactions state, and im console logging out the length. At first i receieve 5, and 5 are rendered out. Then it says the length is 10, but still only 5 have rendered out. After a while loads will suddenly render out.
const [transactions, setTransactions] = useState([])
const { data, loading } = useSubscription(SUBSCRIPTION_TXS, {
onData: (data) => {
console.log('completed data is: ', data) // 1 object
const newTransactions = data.data.data.createTransfer
// setTransactions([newTransactions, ...transactions]) // Tried just tacking on the transactions initially
setTransactions(prevTransactions => { // tried using prev state as suggested in another post to overcome delays
const modifiedArray = [newTransactions, ...prevTransactions]
modifiedArray.sort((a, b) => moment(b.timestamp).format('X') - moment(a.timestamp).format('X'))
modifiedArray.splice(50) // Only display max 50 transactions
return modifiedArray
});
},
onError: (error => {
console.log('[ERROR]: ', error)
})
})
useEffect(() => {
console.log('transactions are: ', transactions) // logs 5 - and 5 are displayed. then logs 10 but only 5 are still displyed.
}, [transactions])
Any suggestions on what I could try please?
UPDATE
Mapping out is simply
<ul className={`space-y-5`}>
{transactions && transactions.map((transaction) => (
<TxCard transaction={transaction} key={transaction.id} />
))}
</ul>

Related

Reading all the messages at once in a chat app (updating a state multiple times) React.js

I have a simple chat component, as a part of a bigger app.
After logging in, userMessages are fetched from the backend and stored in useState.
const [userMessages, setUserMessages] = useState<ChatMessage[]>([]);
const [messages, setMessages] = useState<ChatMessage[]>([]);
userMessages - all the messages addressed to the user (from other users). Based on them, unread messages are displayed.
messages - messages belonging to a given conversation (between two users) are fetched when entering a given conversation, appear in the chat window.
When a user gets a new message while not being on chat, he gets notifications about unread messages (I used socket.io).
After clicking on the blue arrow icon, the current conversation is set (based on the message property - currentConversationId) and the messages belonging to this conversation are fetched from the database.
When they appear in the chat window each received message (only the green ones) is read...
...each message.tsx component has an useEffect that sends a request to the backend to change the status of a given message from unread to read and returns this message to the frontend, then the messages are updated using useState).
# message.tsx
useEffect(() => {
!own && !read && onReadMessage?.(_id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
# communicationContext.tsx
const onReadMessage = async (id: string | undefined) => {
const updatedMessage = await CommunicationService.readMessage(id);
if (updatedMessage) {
let notification = {
receiverId: updatedMessage.sender?._id,
text: "read your message.",
silent: true,
read: false,
type: "message",
};
handleAddNotification?.(notification);
handleSendNotification?.({
...notification,
senderId: updatedMessage.sender?._id,
senderName: updatedMessage.sender?.name,
payload: { message: updatedMessage },
});
const updatedMessages = messages.map((message) =>
message._id === updatedMessage._id ? updatedMessage : message
);
setMessages(updatedMessages);
const updatedUserMessages = userMessages.map((message) =>
message._id === updatedMessage._id ? updatedMessage : message
);
setUserMessages(updatedUserMessages);
}
}
A request containing an updated message is also sent to the sender of the message via socket.io, then useState is also fired on the sender side and they see that the message has been read.
Up to this point everything works fine, but...
the problem arises when there are several unread messages at the same time.
In the database all messages are updated but the application status shows only 1-3 latest messages as read (depending on how many there are - sometimes only the last one is updated).
I know how useState works, so I expected this result, but I'm looking for a way around it and I'm out of ideas.
I need a solution that will update the entire state of the application, not just recent changes, without having to refresh the page.
I tried useReducer but got lost because there are too many useStates in communicationContext.tsx (here is a simplified version).
I suspect your onReadMessage handler should use functional state updates to eliminate race conditions or updating from stale state. It's a trivial change. The enqueued functional state updates will correctly update from the previous state versus whatever (likely stale) state value(s) are closed over in callback scope.
const onReadMessage = async (id: string | undefined) => {
const updatedMessage = await CommunicationService.readMessage(id);
if (updatedMessage) {
const notification = {
receiverId: updatedMessage.sender?._id,
text: "read your message.",
silent: true,
read: false,
type: "message",
};
handleAddNotification?.(notification);
handleSendNotification?.({
...notification,
senderId: updatedMessage.sender?._id,
senderName: updatedMessage.sender?.name,
payload: { message: updatedMessage },
});
const mapMessage = (message) => message._id === updatedMessage._id
? updatedMessage
: message;
setMessages(messages => messages.map(mapMessage));
setUserMessages(userMessages => userMessages.map(mapMessage));
}
};

How to use the `PerformanceNavigationTiming` API to get page load time?

I am trying to to use the PerformanceNavigationTiming API to generate a page load metric.
The MDN API document linked above says that the PerformanceEntry.duration should give me what I need because it:
[r]eturns a timestamp that is the difference between the PerformanceNavigationTiming.loadEventEnd and PerformanceEntry.startTime properties.
However, when I check this property, I get simply 0. I'm accessing this API from within a React hook that runs a useEffect function that wait for the window load event and then checks the api like so:
export const useReportPageLoadTime = () => {
useEffect(() => {
const reportTime = () => {
let navPerformance: PerformanceEntry
navPerformance = window.performance.getEntriesByType('navigation')[0]
console.log({
duration: navPerformance.duration,
blob: navPerformance.toJSON()
})
}
if (document.readyState === 'complete') {
reportTime()
return null
} else {
window.addEventListener('load', reportTime)
return () => window.removeEventListener('load', reportTime)
}
}, [])
}
As you can see there, I also call toJSON on the performance entry and indeed it shows that the values upon which duration (startTime and loadEventEnd) are both 0 as well:
Does anyone know why I am getting this value?
I was finally able to get this to work using a different method than the event listener. It certainly is logical that the data should be ready when the load event fires, but the only way I was able to get the data was to use another feature of the Performance API: the PerformanceObserver, which fires a callback when a new piece of data has become available.
Here is the code that worked for me:
export const useReportPageLoadMetrics = () => {
useEffect(() => {
const perfObserver = new PerformanceObserver((observedEntries) => {
const entry: PerformanceEntry =
observedEntries.getEntriesByType('navigation')[0]
console.log('pageload time: ', entry.duration)
})
perfObserver.observe({
type: 'navigation',
buffered: true
})
}, [])
}

React when useState change react-complex-tree not rerender

codesandbox link
I am using react-complex-tree to show my XML in tree format. I want to add button called ExpandAllTree
const [expandedItems,setExpandedItems] = useState([]);
I am holding my tree Items here
const traverseXml = (treeData, xmlObject) => {
treeData[xmlObject.name] = {
index: xmlObject.name,
canMove: false,
hasChildren: !!xmlObject.children.length,
children: xmlObject.children.map(c => c.name),
data: !xmlObject.children.length ? `${xmlObject.name}: ${xmlObject.value}` : xmlObject.name,
canRename: false
};
if (!xmlObject.children.isEmpty) {
xmlObject.children.forEach(c => {
setExpandedItems(oldArray => [...oldArray,xmlObject.name]);
traverseXml(treeData, c);
});
}
};
this code piece does travel all XML and create a data for react-complex-tree and also gets all Id's of tree element with
setExpandedItems(oldArray => [...oldArray,xmlObject.name]);
This part works perfect.
useEffect(() => {
setExpandedItems([]);
traverseXml(firstTreeData, DocumentXml);
traverseXml(secondTreeData, AppHdrXml);
}, [treeOpen]);
const handleOpenClick = () => {
setTreeOpen(!treeOpen);
}
whenever a button hits it should rerender. But It is not rendering. when I check the logs on first time page open
expandedItems is empty like an expected when I press the button expandedItems to get all tree IDs like expected again but in frontend nothings changes.
<UncontrolledTreeEnvironment
canDragAndDrop={true}
canDropOnItemWithChildren={true}
canReorderItems={true}
dataProvider={new StaticTreeDataProvider(secondTreeData, (item, data) => ({...item, data}))}
getItemTitle={item => item.data}
viewState={{
['Apphdr']: {
expandedItems:expandedItems
},
}}
>
<Tree treeId={"Apphdr"} rootItem={"AppHdr"}/>
</UncontrolledTreeEnvironment>
And there is no mistake on data type with expandedItems because when I give data manually which expandedItems gets, Tree shows as expanded.
When you changed to ControlledTreeEnviroment and set items like
items={firstTreeData}
Tree start to rerender

Socket.io - React: How to display the users connected in a room?

I'm trying to map the users connected to my chat app and render them in a div.
I'm saving the users in an array of objects and emitting an event updating the array when the user connects and disconnects from the chat room in the backend and getting the object in the frontend, saving it in an array and using map to display the users.
When i map the array to display the users my page just shows the current user and the subsequent users, not showing the previous users connected. For example, if I open the app with the name "Bruce" and another page with the user "Andrew", the first page will show both users and the second only "Andrew". If I console.log the array I'm getting from the backend it shows both users. If I exit any page or reload it, it shows "TypeError: Cannot read property 'username' of undefined"
Backend
const users = []
// Storing user
const user = { id, username, room }
users.push(user)
const getUsersInRoom = (room) => {
return users.filter((user) => user.room === room)
}
socket.on('join', ({ username, room }, callback) => {
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room)
})
callback()
})
socket.on('disconnect', () => {
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room)
})
}
})
Frontend
const [usersName, setUsersName] = useState([]);
useEffect(() => {
socket.on('roomData', ({room, users}) => {
setUsersName(previousUsersName => [...previousUsersName, users]);
})
},[]);
return (
<div>
{
usersName.map((name, i) => (
<div key={i}>
<p>
<span>{name[i].username}</span>
</p>
</div>
))
}
<div/>
When I console.log(usersName) it shows the array in this format:
[Array(1)]
0: Array(1)
0:
id: "yXXo7TXVHWN1bzl6AAAV"
room: "Games"
username: "Bruce"
I believe the backend is correct since I'm getting all users. The problem I think is how i'm mapping the array using "name[i].username" since when I reload the page it breaks it and the page renders the names incorrectly as stated above.
Any help is appreciated, If it's confusing or I'm missing some data, please let me know.
It was the way I was displaying the names in the map using "name[i].username".
I guess because the index was updating and breaking the application, and when a new user entered the page it would not display the predecessors because the index was poiting at the new user.
I got it to work by mapping the first array containing the array of objects that I was receiving in the backend and mapping the array of objects itself to display the names.
Also the way I was saving the array in my state was wrong. Since I'm receiving all the connected users there was no need to populate the array with the previous users and then adding the new ones.
here are the changes
const [usersName, setUsersName] = useState([]);
socket.on('roomData', ({room, users}) => {
setUsersName([users]);
})
{
usersName.map((name,index) =>
<div key={index}>
{
name.map((username, subindex) =>
<p key={subindex}>{username.username}</p>
)
}
</div>
)
}

Array of JSON object is not empty but cannot iterate with foreach, show zero length [duplicate]

This question already has an answer here:
How to get data from firestore DB in outside of onSnapshot
(1 answer)
Closed 3 years ago.
I read a collection about book checkout from firestore in create().
The data is store in checkout array that declared in data().
export default {
name: "checkout-history",
data() {
return {
checkout: []
]
};
},
created() {
db.collection("checkout")
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
id: doc.id, // firebase document id
book_did: doc.data().book_did,
borrowed_date: doc.data().borrowed_date,
copies_did: doc.data().copies_did,
due_date: doc.data().due_date
};
this.checkout.push(data); // push to array
});
});
console.log(this.checkout) // show data, as in the image below
console.log(this.checkout.length) // get 0
console.log(Object.keys(this.checkout).length) // get 0
}
......
When I run console.log(this.checkout);, console show this:
However, I cannot iterate it, the console show this.checkout.length is 0
I also tried to use Object.keys but no luck.
Object.keys(this.checkout).forEach(key => {
const keys = this.checkout[key];
console.log(keys);
});
I really don't know what to do anymore.
I read many answers online and tried most of them, but none of them work.
I guess you are executing your code before the completion of the request.
If you hover over the little blue i icon, it says:
Value below was evaluated just now.
Data is loaded from Firestore (and from most modern web APIs) asynchronously. This means that the rest of your code continues to execute after you start the query, and then when the data comes back from the database, your then() callback is called. This in turn means that all code that needs access to the data from the database, must be inside the then() callback.
db.collection("checkout")
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
id: doc.id, // firebase document id
book_did: doc.data().book_did,
borrowed_date: doc.data().borrowed_date,
copies_did: doc.data().copies_did,
due_date: doc.data().due_date
};
this.checkout.push(data); // push to array
console.log(this.checkout) // show data, as in the image below
console.log(this.checkout.length) // get 0
console.log(Object.keys(this.checkout).length) // get 0
});
});
}
......
Also see:
How to get data from firestore DB in outside of onSnapshot
Why are Firebase APIs asynchronous?

Categories