How to remove the the previous instance created in React useEffect() - reactjs

This is my useEffect().I am making a website using React and alan voice recognition.
What this useEffect() does is when someone enters the country it creates an alanBtn which is basically a button.Now the problem is that when the user selects another country the previous button stays on the page and on top of it a new button is made.
What I want to do is whenever a user selects a different country it destroys the previous button instance and then creates new one.
useEffect(() => {
alanbtn({
key: alanKey,
onCommand: ({ command, articles, number }) => {
if (command === "newHeadlines") {
setNewsArticles(articles);
setActiveArticle(-1);
} else if (command === "highlight") {
setActiveArticle((prevValue) => prevValue + 1);
} else if (command === "open") {
const parseNumber =
number.length > 2
? wordsToNumbers(number, { fuzzy: true })
: number;
const article = articles[parseNumber - 1];
if (parseNumber > 20) {
alanbtn().playText("Please try that again");
} else if (article) {
window.open(article.url, "_blank");
alanbtn().playText("Opening...");
}
}
},
}).setVisualState({ answer: country });
}, [country]);
The country is passed as prop to this component.

Related

React State returning old value after waiting for update with Effect hook?

I'm trying to do something that in my mind is very simple.
I have an array of documents (firebase firestore) and I have a method to fetch From Document with timeStamp A to docuemnt with timeStamp B
In the fetch function that tries to see if the ref id has already been fetched, but the messages inside the fetchUpTo function never updates. While the one I log in the effect hook, updates as expected.
The top Log is the Inside fetchUpTo and the bottom one is the effect one.
The logs are from trying to refetch one of the documents present in the bottom log.
const fetchUpTo = (ref: any) => {
if (isFetching || isAtEnd) {
return
};
if (!messagesSnapShot) {
return;
}
if (messagesSnapShot.size < queryLimit) {
return;
}
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
setIsFetching(true)
const lastVisible = messages[0]
const cQuery = query(collection(fireStore, "scab-chat", chatId, "messages"), orderBy('time', 'desc'), startAfter(lastVisible.data.time), endAt(ref.data.time));
getDocs(cQuery).then(newDocs => {
if (newDocs.size < queryLimit) {
setIsAtEnd(true)
}
const newD = newDocs.docs.map(doc => ({
data: doc.data(),
id: doc.id,
ref: doc
}));
setMessages(s => {
const f = newD.filter(doc => s.findIndex(d => d.id === doc.id) === -1)
return [...s, ...f]
})
})
}
After doing this, I "wait" for the state to update with an Effect hook
useEffect(() => {
if (messages) {
setIsFetching(false)
}
}, [messages])
The problem is I have this small part of the code
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
React state will only rerender you app when the function finishes, so you will only check for the updated messages when you call fetchUpTo again. If you need the updated value on the same function call, try using flushSync.
There is a nice video with Dan Abramov trying to achieve the same as you, I will leave it here for reference: https://youtu.be/uqII0AOW1NM?t=2102
Okay so I fixed it kinda, I had to render a diferent component while the isFetchingState was true so
if(isFetching){return <Loader/>}
and then it worked. I still don't really understand why It didn't work in the first place.

What is the correct way to call updateCachedData on a click event in a component that uses the RTKQ query?

I can only think of storing a reference to updateCachedData somewhere globally and use it in that click event but I am not sure this is the React way of doing this.
I have a notifications feed built with a Socket.IO server.
By clicking on a notification it should get deleted from the list. (The list should show only unread notifications.)
But when deleting from the list I create a new array as state in the notifications pane.
When I receive a new notification, all the deleted notifications return back - this is not what I intended.
How can I change the cache entry, more precisely remove items from it without remaking the request for all the notifications?
There are no error messages.
Code
getNotifications: build.query<
IDbNotification[],
IGetNotificationsQueryParams
>({
query: (params: IGetNotificationsQueryParams) => ({
url: `notifications?authToken=${params.authToken || ""}&limit=${
params.limit
}&userId=${params.userId || ""}${
params.justUnread ? "&justUnread" : ""
}`,
method: "GET"
}),
keepUnusedDataFor: 0,
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }
) {
const { myIo, connectHandler } = getWebSocketConnection(
"notifications",
clone({
subscribtions: arg.userId
? getFollowedUserIds().concat({
uid: arg.userId,
justUnread: arg.justUnread
})
: getFollowedUserIds()
})
);
const listener = (eventData: IDbNotification) => {
if (
(eventData as any).subscriber === arg.userId &&
(!arg.justUnread || typeof eventData.readDateTime === "undefined")
) {
updateCachedData(draft => {
draft.unshift(eventData);
if (draft.length > arg.limit) {
draft.pop();
}
});
}
};
try {
await cacheDataLoaded;
myIo.on("notifications", listener);
} catch {}
await cacheEntryRemoved;
myIo.off("notifications", listener);
myIo.off("connect", connectHandler);
}
})
You can use updateQueryData - updateCachedData is just a shortcut for the current cache entry for convenience.
dispatch(
api.util.updateQueryData('getNotifications', arg, (draft) => {
// change it here
})
)
See this for more context: https://redux-toolkit.js.org/rtk-query/usage/optimistic-updates

Twilio + React: How to change audio or video input device during a call?

I've been looking at the documentation but I can't figure it out. When a user selects a different device, the other users can't hear that person anymore. This must mean that something is going right with the unpublishing of tracks, right? I'm not sure.
This is my code for a user to change devices:
const setDevice = (device) => {
if(!room) return
let deviceId = device.deviceId
const localParticipant = room.localParticipant
if(device.kind === 'audioinput'){
setSelectedAudioDevice(device.label)
Video.createLocalAudioTrack({
deviceId: {exact: deviceId}
}).then((localAudioTrack) => {
const tracks = localParticipant.audioTracks
tracks.forEach((track) => {
localParticipant.unpublishTrack(track.track)
})
localParticipant.publishTrack(localAudioTrack)
})
} else if(device.kind === 'videoinput'){
setSelectedVideoDevice(device.label)
Video.createLocalVideoTrack({
deviceId: {exact: deviceId}
}).then((localVideoTrack) => {
const tracks = localParticipant.videoTracks
tracks.forEach((track) => {
localParticipant.unpublishTrack(track.track)
})
localParticipant.publishTrack(localVideoTrack)
})
}
}
Each participant has its own component in which they subscribe to tracks. However, this code was from one of the Twilio examples, so I'm not entirely sure how it works.
const trackpubsToTracks = (trackMap) =>
Array.from(trackMap.values())
.map((publication) => publication.track)
.filter((track) => track !== null);
useEffect(() => {
setVideoTracks(trackpubsToTracks(participant.videoTracks));
setAudioTracks(trackpubsToTracks(participant.audioTracks));
const trackSubscribed = (track) => {
if (track.kind === "video") {
setVideoTracks((videoTracks) => [...videoTracks, track]);
} else if (track.kind === "audio") {
setAudioTracks((audioTracks) => [...audioTracks, track]);
}
};
const trackUnsubscribed = (track) => {
if (track.kind === "video") {
setVideoTracks((videoTracks) => videoTracks.filter((v) => v !== track));
} else if (track.kind === "audio") {
setAudioTracks((audioTracks) => audioTracks.filter((a) => a !== track));
}
};
participant.on("trackSubscribed", trackSubscribed);
participant.on("trackUnsubscribed", trackUnsubscribed);
return () => {
setVideoTracks([]);
setAudioTracks([]);
participant.removeAllListeners();
};
}, [participant]);
useEffect(() => {
const videoTrack = videoTracks[0];
if (videoTrack) {
videoTrack.attach(videoRef.current);
return () => {
videoTrack.detach();
};
}
}, [videoTracks]);
useEffect(() => {
const audioTrack = audioTracks[0];
if (audioTrack) {
audioTrack.attach(audioRef.current);
return () => {
audioTrack.detach();
};
}
}, [audioTracks]);
If anyone knows how I can handle device switching mid-call, I'd greatly appreciate it.
Twilio developer evangelist here.
I have found that the best order of operations here is:
Unpublish the local participant’s existing track from the room, this will trigger the trackRemoved event on the room for any other participants
Detach the existing track from the page
Stop the track completely
Request the new track with createLocal(Video|Audio)Track
Attach the new track to the page
Publish the new track to the room, triggering the trackAdded event on the room for the other participants
This is especially true for iOS devices which do not let you access more than one camera at a time.
Here is some code I've used before, though not in a React application:
function stopTracks(tracks) {
tracks.forEach(function(track) {
if (track) { track.stop(); }
})
}
function updateVideoDevice(event) {
const select = event.target;
const localParticipant = activeRoom.localParticipant;
if (select.value !== '') {
const tracks = Array.from(localParticipant.videoTracks.values()).map(
function(trackPublication) {
return trackPublication.track;
}
);
localParticipant.unpublishTracks(tracks);
detachTracks(tracks);
stopTracks(tracks);
Video.createLocalVideoTrack({
deviceId: { exact: select.value }
}).then(function(localVideoTrack) {
localParticipant.publishTrack(localVideoTrack);
log(localParticipant.identity + ' added track: ' + localVideoTrack.kind);
const previewContainer = document.getElementById('local-media');
attachTracks([localVideoTrack], previewContainer);
});
}
}
You can see the entire application in this repo on GitHub and I wrote about it here.
I think the React example you're referring to was one of mine too. I actually had a go at adding camera changes to that repo on a branch. It was apparently a year ago, but you can see the updates here. Hopefully that can point you in the right direction too.

adding item to cart in reactjs using localStorage

whenever i am adding item in cart it get added in cart on page but not on localstorage on first click but my total price updates according to the item selected price. And when i add next item it gets added on localstorage as well as the first selected item gets added.
Everything is working properly but problem is on first click item is not displaying on localstorage.Please help , i am stuck with this problem since more then 2days,Thank you in advance!
componentDidMount() {
var localItems = JSON.parse(localStorage.getItem('items')) || [];
this.setState({
selectedProduct : localItems
});}
addToCart(image, name, type,price, id) {
const found = this.state.selectedProduct.some(el => el.id === id );
const obj = {image,name,type,price,id};
this.setState(
{
selectedProduct: found ? this.state.selectedProduct : [...this.state.selectedProduct,obj],
isAdded: true,
totalPrice : found ? this.state.totalPrice : this.state.totalPrice + parseInt(price, 10)
},
() => {
localStorage.setItem('total',this.state.totalPrice);
localStorage.setItem("items", JSON.stringify(this.state.selectedProduct));
}
)
}
Please see these images of my output for more clearification of statement
When first item is selected (on first click):
When i click on Second Item:
You can't use this.state.totalPrice inside the second parameter of setState, it will still be the old variable.
Instead do it all inside a callback method in setState, that way you'll get correct access to the previous state:
addToCart(image, name, type, price, id) {
const obj = {
image,
name,
type,
price,
id
};
this.setState((prevState) => {
const found = prevState.selectedProduct.some(el => el.id === id);
const selectedProduct = found ? prevState.selectedProduct : [...this.state.selectedProduct, obj];
const totalPrice = found ? prevState.totalPrice : prevState.totalPrice + parseInt(price, 10)
localStorage.setItem('total', totalPrice);
localStorage.setItem("items", JSON.stringify(selectedProduct));
return {
selectedProduct,
totalPrice,
isAdded: true
};
})
}

ReactJS: setState() value is rendering in console.log but not updating in state

Here I am getting small issue with setState() . I know this setState() is an asynchronous function. I have gone some example from SO example as well and I am getting my value in console.log() but this same is not updating in state.
What should I need to change here to get the updated state() value. Any suggestion for me please.
Here in my code on tab change the state value should be updated for form submission.
//Code
handleOnSubmitJoinUS(e) {
e.preventDefault();
const { name, phone, email, comment, activeTab } = this.state;
if (activeTab == 0 ) {
this.setState({comment: "Message for : Becoming a team member"}, () => {
console.log(this.state.comment);
});
} else if (activeTab == 1) {
this.setState({comment: "Message for : Becoming a Vendor Partner"}, () => {
console.log(this.state.comment);
});
} else {
this.setState({comment: "Message for : Becoming a Designer Associates"}, () => {
console.log(this.state.comment);
});
}
const sendingMsgData = {
name, phone, email, comment
}
//attempt to login
if (sendingMsgData.email && sendingMsgData.name && sendingMsgData.phone != null ) {
this.setState({msg : "Thank you! we recieved your submission successfully, we will surely contact you within 24 hours"});
window.setTimeout(function(){window.location.reload()}, 1000);
}
this.props.sendingConsultation(sendingMsgData);
}
As you said, setState is asynchronous, so the rest of the function continues running, doing all that message sending and so on before the state is actually set in this.state. For your original code, that'd mean the rest of the code should be in that post-setState callback.
However, it doesn't look like comment needs to be in the state at all, since you're deriving it from state.activeTab:
handleOnSubmitJoinUS(e) {
e.preventDefault();
const { name, phone, email, activeTab } = this.state;
let comment;
if (activeTab == 0) {
comment = "Message for : Becoming a team member";
} else if (activeTab == 1) {
comment = "Message for : Becoming a Vendor Partner";
} else {
comment = "Message for : Becoming a Designer Associates";
}
const sendingMsgData = {
name,
phone,
email,
comment,
};
//attempt to login
if (sendingMsgData.email && sendingMsgData.name && sendingMsgData.phone != null) {
this.setState({ msg: "Thank you! we recieved your submission successfully, we will surely contact you within 24 hours" });
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
this.props.sendingConsultation(sendingMsgData);
}
If you do really need it in state for some reason, you can do a this.setState({comment}) at the end.

Resources