useState doesn't run correctly in websocket connection - reactjs

Hello guys I had a problem. I don't know it is a bug or not but I think it is a bug. I would open an issue on github repo of react but first I wanted to look at here and ask to you. I will give you all my code on the bottom of this post
What is the problem?
I am connection a socket when my component first mounted and catched the user to connect live chat. And I use useState to set messages when I or agent send a messsage on message event. I use useState on the line of 114. First of all this useState(setMessages on ) works correctly. But If I set the setMessages by the other ways this won't work.
For Example 1:
If I set this setMessages like below, setMessages works correctly and changes state but messages state not rendering on DOM correctly. I didn't udnerstand what's wrong.
const dummy = [...messages]
dummy.push(data.messageData)
setMessages(dummy)
For Example 2:
If I set this setMessages like below, setMessages doesn't work and I got an error and I never see my rendered page because it say ...prev is a null. I didn't understand what's wrong again.
setMessages((prev) => [...prev, data.messageData])
For Example 3:
This is the most interesting part of my code. It's really impossible one. If I remove line 111 and 112 (those are below here) the version of runs correctly of my codes doesn't work and setMessages doesnt work by the way with concat either. And I don't use those codes anywhere you can see that by using Ctrl+F. I just described those to set setMessages with the dummy variable but that didn't work and now I can't delete them because if I delete them my working codes won't work either.
const dummy = messages
dummy.push(data.messageData)
Now guys what's wrong on there. I fight all day with this buy I didnt find any reason. Is it a bug or not? Have a good days I will be here for your replies.
This is all of my codes in the page that I have a problem
https://gist.github.com/mucahidyazar/5ccff6d67d95d23dfb470dfe026f714b

You are using the useState incorrectly and its not a problem with react ;)
You need to create a new object reference if you use setState.
For example a string, number or boolean are always new instances.
But this does not work for objects. Push keeps the old object reference and setting a value to the same reference as before to the useState hook will be skipped.
const dummy = messages
dummy.push(data.messageData)
setMessages(dummy)
You are passing he same object to the setState (here setMessages) than before (push only adds the item to the same obejct), but you need a new instance instead, or else react will not rerender.
setMessages((prev) => [...prev, data.messageData])
but this only works if the default item is an array, so make sure you are setting ... = useState([]) so that prev is defined.
For line 111, yes the current object receives the new items, so the console.log shows the correct data,but since its the same instance (push does not create a new instance of the array), the rerender gets skipped and you will not see any changes on the resulting website.
o when I take a look at your code, change this:
const dummy = messages
dummy.push(data.messageData)
console.log(dummy)
setMessages(messages.concat(data.messageData).slice(0, messages.length)
to
setMessages([...messages, data.messageData])

Related

How to emit side effect using new state directly after updating state

I have some code like this:
const [_channel, ably] = useChannel(channelName, msg => {
if (msg.type) === type1 {
setStateVariable(prev => new_from_prev(prev));
// Need the updated state for this
if (condition(stateVariable)) {
// This line attempts to send a socket message
sendMessage(/* someData */);
}
}
})
useChannel is a wrapper around useEffect with dependency array [channelName]; you can see the source code here: https://github.com/ably-labs/react-hooks/blob/main/src/hooks/useChannel.ts
I am not able to access the updated state this way because setStateVariable does the state update asynchronously. This does not work with refs either for the same reason. I am aware that I can use the callback to setStateVariable, however there are several problems with this. First, it violates seperation of concerns and is clearly an anti-pattern. Second, in React Strict Mode, setStateVariable will run twice, sending my socket message twice, which I don't want. The only solution I can think of is putting the code that needs to access the updated state into a setTimeout with an arbitrary timeout length. I have tried this and it works. However, this is obviously a hack and not reliable. There must be a better way to do this. Any help?

RxJS and repeated events

I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.

React updating State from Firebase Server

I have a firebase database, that has a collection called "post" and in post there 6 variables (displayName, userName, verified, text, image, avatar). The idea is, there will be multiple posts in the database.
React Code:
const [posts, setPosts] = useState([]);
//Whenever the firebase database changes, it runs this method
useEffect(() => {
db.collection("posts").onSnapshot((snapshot) =>
//Loops through all the posts and adds the data into an array
setPosts(snapshot.docs.map((doc) => doc.data()))
);
}, []);
In react, I have two state variables, posts and setPosts. I'm assuming they are initially just set to empty arrays.
Now I have the useEffect function, that I am told runs whenever the database changes/updated. First question, how does the function know that the database updated? In other words, how does the useEffect function work?
Secondly, I'm pretty sure in the end, the post variable becomes a list of all the post objects in the database. I'm not sure how that happened. I have attached the code that updates this state above, but I'm not too sure how it works. Can you please break it down and explain how it works? I'm also not sure what the setPosts state is used for.
Please let me know!
In the above
In line 1 - You have used State hooks to set up posts as an empty Array. More reading can be done here to understand what state hooks mean - https://reactjs.org/docs/hooks-state.html
Next you set up a useEffect hook function (https://reactjs.org/docs/hooks-effect.html) to make a backend (firebase) api call after rendering.
Inside the hook function you are looking up data from the posts collection in firebase and bringing back a snapshot of all the documents in that collection. db.collection("posts").onSnapshot(callBack). The callback function is called every time something changes on the underlying database using well known observer pattern (read more in following links https://rxjs-dev.firebaseapp.com/guide/overview, https://firebase.google.com/docs/reference/node/firebase.firestore.CollectionReference#onsnapshot)
Then in the onSnapshot callback function you get an array containing documents which is further mapped to an output array using the javascript Map function snapshot.docs.map((doc) => doc.data()). https://www.w3schools.com/jsref/jsref_map.asp
Finally this output array is set in the posts variable using the
setPosts() method.
Hope this breakdown helps and I suggest reading the links in detail so its clear how everything comes together.

React stackable snackbars/toasts

I'm creating my own simple snackbar/toast stacker. However, I'm having problems with queing them in an orderly manner. Removing a snackbar from the snackbar que causes re-render and odd behavior.
The basic flow:
Click a button which causes the addSnack function to fire which is provided by the withSnackbar HOC.
Take the parameters from the fired function, and create a snack accordingly and add it to the snackbar list.
At the end, we render the snackbar list.
Each snackbar controls it's own appearance and disappearance, and is controlled by a time out. After the timeout is fired, it calls removeSnack function which is suppose to remove the first snack from the list.
codesandbox
If you click the button for example, four times in a short amount of time. They render nicely, but when the first one is to be deleted, they all disappear and reappear abnormally.
I understand that it's partially the state re-renderings fault, however, I'm not sure how to handle it in a way that the removal is handled gracefully without affecting the rendering of other snacks.
So, after many hours of trial and error, I found a solution that works so far. Moving and reading the snacks outside of the state helped with the bizarre rendering problems, and with it, I was able to create a message que which works well.
Working example
Codesandbox
If you look at splice document, you will notice that it's returning an array of deleted elements and not the initial array.
You can correct it by splicing then updating:
snacks.splice(-1, 1);
addSnacks(snacks);
However you are still going to have some weird behavior and you might need to use a keyed list to fix that.
i had the same issue and i saw your solution, but i was really trying to find out why it happens - here is why:
when u call a useState hook from an async function's callback, you should use the callback format of the hook to make sure that you are working with the latest value. example:
const [messages, setMessages] = useState([]);
const addMessage = ( message ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
return [ ...prevMessages, message ];
});
};
const removeMessage = ( index ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
let newMessages = [...prevMessages];
newMessages.splice( index, 1 );
return newMessages;
});
};

Why "selected" not rendered here?

I am trying to understand how React works with the new Hooks implementation. In this example, I want the browsers to render selected items as I click on the rendered options. But as you can see, it doesn't work.
Here is the example: https://codesandbox.io/s/pjorxzyrx7
Do I have to use the useEffect in this case? Also, as I understand, useEffect couldn't render anything and only return functions. So, what am I missing here?
Thank you!
You're currently mutating the contents of the selected array instead of replacing it. React can't detect a state change when you do this.
Try something like the following:
const handleSelected = item => {
console.log(item);
console.log(selected);
setSelected([...selected, item]);
};
When updating arrays or objects as a part of a state, always make a new copy to assign so that React can properly know when to re-render.
Also, include relevant parts of your code directly in the question in the future, instead of hiding it behind a link (although including a runnable example is great!)

Resources