How to reset channel of window.echo on changing of route reactjs - reactjs

I am using laravel-websockets to listen to event. I have no issue on the back-end side; The issue is on the front-end side.
SCENARIO:
When I go to a specific route post/[slug], the current channel is based on the current slug. When I redirect to the same route but different value of slug, the channel listens to the first value on page refresh and not to the current one.
const Component = () => {
const router = useRouter();
const {slug} = router.query;
useEffect(() => {
window.Echo.private(`post.${slug}`).listen('PrivateEvent', e => {
console.log(e)
});
}, [slug])
}
Example:
On page refresh, go to post/first-slug. Next, click to <Link to="/post/second-slug">About</Link>
The example above should listen to second-slug and not the first-slug.
How can I solve this without hard refresh or <a> tag?

You forgot to stop listening on the previous channel, so the events are still received. I suppose that you end up with two active channels, receiving events for both.
Inside a useEffect() you should return a cleanup function that clears resources created for the effect
Here is how:
const Component = () => {
const router = useRouter();
const {slug} = router.query;
useEffect(() => {
window.Echo.private(`post.${slug}`).listen('PrivateEvent', e => {
console.log(e)
});
return () => window.Echo.private(`post.${slug}`).stopListening('PrivateEvent');
}, [slug])
}
If this does not solve your problem, please:
display the slug in your component (return <div>slug</div>;) to confirm that the navigation really happens ;
show us the whole console log.

Related

react navigate and save history

I am building a React Application with multi-router
Home router call an API in use Effect but when I navigate to another Route and go back to home the request is recall and the component which contain response is reload
is there a way to save history so when I come back the route not calling the API and if it call it, at least not reload the section using response
here my Use-effect
useEffect(() => {
(async () => {
try{
const response = await axios.get("user")
dispatch(setAuth(response.data))
}
catch(e){}
try{
const response = await axios.get("get_all_posts")
setpostsInfo(response.data)
}
catch(e){}
})()
}, []);
Thanks for help
add this isRun
const [ isRun ,setIsRun ] =useState(true)
useEffect( () => {
if(isRun){
(async () => {
try{
const response = await axios.get("user")
dispatch(setAuth(response.data))
}
catch(e){}
try{
const response = await axios.get("get_all_posts")
setpostsInfo(response.data)
setIsRun(false)
}
catch(e){}
})()
}
}, []);
when you change the route you component unmount so its state is lost.
when you go back to the home route the component mount again it's a new instance so you can't hold the information in the component you should hold the information of the number of visiting the page for example or if it's the first time mounting the component in a higher place than the component (the localstorage for example) you can store a key or value to indicate that it's the first time visiting this page and when the compoenent unmount the information stills there. when the component mount again check the existance and validity of the key in the localstorage and you decide whether you send the request or not in the useEffect

React Router how to update the route twice in one render cycle

I think it is an obvious limitation in React & React Router, but I'm asking maybe someone will have another idea, or someone else that will come here and will understand it impossible.
I have two custom hooks to do some logic related to the navigation. In the code below I tried to remove all the irrelevant code, but you can see the original code here.
Custom Hook to control the page:
const usePageNavigate = () => {
const history = useHistory()
const navigate = useCallback((newPage) => history.push(newPage), [history])
return navigate
}
Custom Hook to constol the QueryString:
const useMoreInfo = () => {
const { pathname } = useLocation()
const history = useHistory()
const setQuery = useCallback((value) => history.replace(pathname + `?myData=${value}`), [history, pathname])
return setQuery
}
Now you can see the *QueryString hook is used the location to concatenate the query string to the current path.
The problem is that running these two in the same render cycle (for example, in useEffect or in act in the test) causes that only the latest one will be the end result of the router.
const NavigateTo = ({ page, user }) => {
const toPage = usePageNavigate()
const withDate = useMoreInfo()
useEffect(() => {
toPage(page)
withDate(user)
}, [])
return <div></div>
}
What can I do (except creating another hook to do both changes as one)?
Should/Can I implement a kind of "events bus" to collect all the history changes and commit them at the end of the loop?
Any other ideas?

How to display a badge when a chat thread has unread messages React JS, Firestore

I am developing a chat application in React and firebase firestore. I want to display an unread messages badge every time a new message is added to the database. Currently, I am using a useEffect hook to check if the last message is read, and that only works only the first time the page renders - the unread messages badge appears only after I reload the page. I think what I can't figure out is how to re-render every time the state changes. Kindly look at my code and tell me what I am missing. I hope my issue is clear. Thanks in advance.
const [chatMessages, setChatMessages] = useState([])
const [isRead, setIsRead] = useState(false)
const readsRef = db.collection('rooms').doc(id).collection('messages')
useEffect(() => {
const unsubscribe = readsRef.orderBy('timestamp', 'desc')
.limit(1).onSnapshot((snapshot) => {
snapshot.docs.map((snap) => {
setChatMessages(snap.data().message)
readsRef.doc(snap.id).collection('read').doc(userID.uid).onSnapshot((snapshot1 => {
if (snapshot1.get('readReceipt') === userID.uid) {
setIsRead(true)
}
}))
})
})
return unsubscribe;
}, [isRead])
return (
<SidebarOptionChannel>
<span># </span>{title} - {chatMessages}<span>{isRead ? null : <UnreadBadge>
<span>1</span></UnreadBadge> }</span>
</SidebarOptionChannel>
)
Another solution to your problem could be to update your chat application by listening for changes in your Database of Firebase. This can be done by using the methods of firebase.database.Reference:
on() or once()
Here is an example provided by the documentation of Firebase:
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
const data = snapshot.val();
updateStarCount(postElement, data);
});

Different WebSocket "onmessage" handlers depending on screen

My React Native app (iOS and Android) uses a single global WebSocket connection.
I want to respond to the same messages from the server differently depending on the screen, e.g. one way on the home screen (A) and differently on screen B.
Because the home screen is still mounted and "active" after screen B has been opened, presumably I can't just overwrite the websocket "onmessage" handler as that would lead to inconsistencies.
Can anyone point me in the right direction for what I'm trying to achieve?
Without seeing some code on what you're trying to achieve, I think what you want in general is a subscription model. You have only one handler registered with the socket, but that handler can delegate to other functions that can be added/removed as desired.
In general, I would recommend creating the websocket connection somewhere in the react tree such as a hook with a context provider. To avoid cluttering up the idea here, though, let's assume your websocket connection is defined a static context (i.e. in a module, rather than a component or hook).
// socket.js
const messageHandlers = new Set()
export const addMessageHandler = (handler) => {
messageHandlers.add(handler)
}
export const removeMessageHandler = (handler) => {
messageHandlers.delete(handler)
}
const socket = new WebSocket('ws://example.com')
socket.onmessage = (event) => {
messageHandlers.forEach((handler) => handler(event))
}
Then in your screen:
import { addMessageHandler, removeMessageHandler } from '/path/to/socket.js'
const SomeScreen = () => {
useCallback(() => {
const handler = (event) => { /* do something with event */ }
addMessageHandler(handler)
return () => removeMessageHandler(handler)
}, [])
}
This will keep the listener alive even if the screen is in the background. If you're using react-navigation, you can also make it register only while the screen is the current screen using useFocusEffect such as:
useFocusEffect(
useCallback(() => {
const handler = (event) => { /* do something with event */ }
addMessageHandler(handler)
return () => removeMessageHandler(handler)
}, [])
)

react native mapStateToProps and React.useEffect render issue

I have a project that needs to show a register modal when the user is null,
it works some times??
I am asking a state element if user exists, then showing the modal, the problem is that when the user is logged in, when the page is shown, sometimes it shows the register modal,
like it renders and then checks if user === null
Is that my problem?
NOTE: on other tabs, this works fine, like it had more time to load the state?
const mapStateToProps = ({ firebase, navigation }) => ({
firebase,
})
function Feed ({ feed, firebase }) {
React.useEffect(
() => navigation.addListener('focus', () =>
{
console.log("aki:: ",firebase.user)
if (firebase.user === null) {
//SHOW MODAL TO INVITE REGISTER
setVisible(true),
navigation.dispatch(navigateToBrowse())
} else {
setVisible(false)
}
}
),
[]
);
It's likely because the firebase.user prop isn't set before this component is rendered. It's impossible to tell without seeing the parent component.
You can block the tree from rendering until firebase.user exists.
Otherwise, you have to differentiate between 1. auth is loading, 2. auth is done loading and user doesn't exist, and 3. auth is done loading and user exists.
I initially thought it was because of how you were handling the navigation side effect, so here's that code anyways:
function Feed({ feed, firebase }) {
const [visible, setVisible] = useState<boolean>(false);
React.useEffect(() => {
const checkForUser = () => {
setVisible(firebase.user === null);
}
// Check for user on all focus events
navigation.addListener('focus', checkForUser);
// Also check for user immediately
checkForUser();
}, []);
React.useEffect(() => {
// Only navigate away when visible gets turned on
if (visible) navigation.dispatch(navigateToBrowse());
}, [visible])
}

Resources