How to make count increment with Websocket api using React JS - javascript

I have notification icon button which has count so i want the count to be incremented whenever new data is fetched using Websocket api,how can i achieve that,and how make this websocket as component which will recieve prop
import React from "react";
import { useState, useEffect } from "react";
const Websocket = (props) => {
const [count, setCount] = useState([]);
useEffect(() => {
let ws = new Websocket("wss://example.com");
ws.onopen = () => {
console.log("connection open");
};
ws.onmessage = (e) => {
setCount(e.data);
};
});
};
export default Websocket;

So if i get you right, i'm guessing you're trying to increment count from lets say 0 to the value return from the socket? If so then always remember that the setter function of a hook can take a callback with the current state as its parameter, hence:
import React from "react";
import { useState, useEffect } from "react";
const Websocket = (props) => {
const [count, setCount] = useState([]);
useEffect(() => {
let ws = new Websocket("wss://example.com");
ws.onopen = () => {
console.log("connection open");
};
ws.onmessage = (e) => {
setCount(state => state + e.data);
};
});
};
export default Websocket;
This should work if that's what you mean in your question.

Related

Why is my function not dispatching in react component?

why is fetchReviews not fetching?
Originally didn't use fetchData in use effect.
Ive tried using useDispatch.
BusinessId is being passed into the star component.
no errors in console.
please let me know if theres other files you need to see.
thank you!
star component:
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import {AiFillStar } from "react-icons/ai";
import { fetchReviews } from '../../actions/review_actions';
function Star(props) {
const [rating, setRating] = useState(null);
// const [reviews, setReview] = useState(props.reviews)
// const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
await fetchReviews(props.businessId)
};
fetchData();
console.log(props);
// getAverageRating();
});
const getAverageRating = () => {
let totalStars = 0;
props.reviews.forEach(review => {totalStars += review.rating});
let averageStars = Math.ceil(totalStars / props.reviews.length);
setRating(averageStars);
}
return (
<div className='star-rating-container'>
{Array(5).fill().map((_, i) => {
const ratingValue = i + 1;
return (
<div className='each-star' key={ratingValue}>
<AiFillStar
className='star'
color={ratingValue <= rating ? '#D32322' : '#E4E5E9'}
size={24} />
</div>
)
})}
</div>
);
};
export default Star;
star_container:
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import Star from "./star";
import { fetchReviews } from "../../actions/review_actions";
const mSTP = state => {
return {
reviews: Object.values(state.entities.reviews)
};
}
const mDTP = dispatch => {
return {
fetchReviews: businessId => dispatch(fetchReviews(businessId))
};
};
export default connect(mSTP, mDTP)(Star);
console image
why is fetchReviews not fetching? Originally didn't use fetchData in use effect. Ive tried using useDispatch. BusinessId is being passed into the star component. no errors in console.
edit!***
made some changes and added useDispatch. now it wont stop running. its constantly fetching.
function Star(props) {
const [rating, setRating] = useState(null);
const [reviews, setReview] = useState(null)
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
ended up just calling using the ajax call in the useEffect.
useEffect(() => {
const fetchReviews = (businessId) =>
$.ajax({
method: "GET",
url: `/api/businesses/${businessId}/reviews`,
});
fetchReviews(props.businessId).then((reviews) => getAverageRating(reviews));
}), [];
if anyone knows how i can clean up and use the dispatch lmk.
ty all.
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
dependency array is outside the useEffect. Since useEffect has no dependency option passed, function inside useEffect will run in every render and in each render you keep dispatching action which changes the store which rerenders the component since it rerenders code inside useEffect runs
// pass the dependency array in correct place
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
},[]), ;
Passing empty array [] means, code inside useEffect will run only once before your component mounted

Not able to listen to a socket event in ReactJs component

If I am trying to call the socket.on outside the component, It is working but when I use it in useEffect() It is not working?
import ChatBubble from "../components/Chatbubblebot";
import io from 'socket.io-client'
let data = [];
function Messagelist({ sendMessage }) {
data.push(sendMessage);
useEffect(() => {
const socket = io.connect("http://localhost:5000")
socket.on("admin",(payload) => {
console.log("payload",payload);
data.push({who:"him",message:payload})
})
})
let list = () => {
console.log("sendMessage",sendMessage.length);
if (data === undefined && sendMessage.length === 0) {
} else {
return data.map((e) => <ChatBubble who={e.who} message={e.message} />);
}
};
return <ul>{list()}</ul>;
}
export default Messagelist;```
Try defining the socket variable outside component.
var socket;
And then in the component useEffect:
socket = io.connect("http://localhost:5000")
Also,
the let data = []; should not be defined like that.
It should be a state variable of the component like below:
const [data, setData] = useState([]);
And when you get new message from socket:
socket.on("admin",(payload) => {
console.log("payload",payload);
let _data= data;
_data.push({who:"him",message:payload});
setData(_data);
})

How Can I call useCollection hook again with updated parameters

When My Home component(image above) mounts for the first time, it gives call to the hook "useCollection" with the arguments below, Now how can I call useCollection hook again with a different set of arguments for filtering my list,
It gives error calling to a function by using onClickButon handlers as well I m not able to use "UseState" as useState does not call the above hook again.
useCollection Hook
import React, { useEffect, useRef, useState } from "react";
import { projectFirestore } from "../firebase/config";
export const useCollections = (collection, _query, _orderBy) => {
const [documents, setDocuments] = useState(null);
const [error, setError] = useState(null);
const query = useRef(_query).current;
const orderBy = useRef(_orderBy).current;
useEffect(() => {
let ref = projectFirestore.collection(collection);
if (query) {
ref = ref.where(...query);
}
if (orderBy) {
ref = ref.orderBy(...orderBy);
}
const unsubscribe = ref.onSnapshot(
(snapshot) => {
let results = [];
snapshot.docs.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id });
});
setDocuments(results);
setError(null);
},
(error) => {
console.log(error);
setError("could not fetch data");
}
);
return () => unsubscribe();
}, [collection, query, orderBy]);
return { documents, error };
};

react hook useContext giving null value in my props.children component

I am new in reactjs hooks. I have a component which opens a socket from the server. Now I want to pass the props from my parent component which is the QueueWebSocket to my other component which is wrapped by my parent component.
Here is my parent component:
import { useEffect, useState, createContext } from 'react';
import { getDjangoWebsocketHost } from 'components/global/function/env';
import { Typography } from '#material-ui/core';
// initialize websocket context
const QueueContext = createContext(null);
// export websocket context
export { QueueContext }
// initialize handler for websocket connection
// before DOM is ready
let wsInit = null;
const QueueWebsocket = (props) => {
const { children } = props;
// websocket payload
const [responsePayload, setResponsePayload] = useState(null);
// handler for websocket connection when DOM is ready
const [ws, setWs] = useState(null);
// state for checking if websocket connection has been established
const [wsReady, setWsReady] = useState(false);
// state for holding data received from the websocket connection
// this is sent across all logged in PGAN users when data is available
// const [globalNotification, setGlobalNotification] = useState(payload);
// function for sending CONNECT event
const connectEvent = ({ requestPayload }) => {
if (wsReady) {
encodedRequestPayload = new Uint8Array(JSON.stringify(
requestPayload).split('').map(
c => c.charCodeAt(0)
)
).buffer;
ws.send(encodedRequestPayload);
}
};
// function for sending message via websocket connection
// const sendMessage = ({ requestPayload }) => {
// console.log(requestPayload)
// let encodedRequestPayload = new Uint8Array(JSON.stringify(
// requestPayload).split('').map(
// c => c.charCodeAt(0)
// )
// ).buffer;
// console.log(encodedRequestPayload)
// ws.send(encodedRequestPayload);
// };
// function for opening connection,
// monitoring closed connection,
// and monitoring incoming data from the websocket connection
const ws_connect = (s) => {
if (s) {
// set 'ws' and 'wsReady' states
// when websocket connection is opened
s.onopen = () => {
setWs(s);
setWsReady(true)
}
// if connection is closed,
// attempt to reconnect after 1 second
s.onclose = (e) => {
setTimeout(() => {
ws_connect();
}, 1000);
}
s.onmessage = (e) => {
// the data received from the websocket connection
let receivedData = e.data;
// the payload is in JSON,
// but our websocket server sends it as binary data
// so we need to parse it to be able for JS to understand
let binary_data = new Uint8Array(receivedData).reduce((p, c) => p + String.fromCharCode(c), '');
receivedData = JSON.parse(binary_data);
// assign the 'payload' object to 'data'
// this time, we now have the parsed value of the websocket message
receivedData = receivedData['payload'];
// deconstruct the payload
// expectedly, we expect the ff. as default:
// message -> any message from the websocket connection
// event -> name of the corresponding event from the websocket connection
// data -> any additional information
const { event, data } = receivedData;
// check what event was invoked
// and perform respective operation
switch(event) {
case "EVENT_CALL":
if(data) {
// console.log(data);
setResponsePayload((prev) => ({...prev, event , data}));
}
break;
default:
break;
}
}
}
}
// function for running ws_connect() function above,
// ONLY when the DOM is ready
const initializeConnection = () => {
if (typeof window !== 'undefined') {
wsInit = new WebSocket(
`${getDjangoWebsocketHost()}/ws/queuePage/test/`);
if (wsInit) {
wsInit.binaryType = 'arraybuffer';
ws_connect(wsInit);
}
}
};
// attempt to connect to 'ws/login/'
// when this component loads
useEffect(() => {
initializeConnection();
}, []);
return (
<QueueContext.Provider
value={{
responsePayload,
}}
>
{ children }
</QueueContext.Provider>
);
}
export default QueueWebsocket;
now i tried to wrapped my other component from the component above to get the responsePayload prop from my children component but it gives me null.
Here is what i tried:
import { useState, useCallback, useMemo, useRef, useContext } from 'react';
import { Row, Col, Divider } from 'antd';
import { makeStyles } from '#material-ui/core/styles';
import { useRouter } from 'next/router';
import QueueWebsocket from 'components/global/function/websocket/queue_service/QueueWebsocket';
import {QueueContext} from 'components/global/function/websocket/queue_service/QueueWebsocket';
const useStyles = makeStyles((theme) => ({
margin: {
margin: theme.spacing(1),
},
paper: {
textAlign: 'center',
color: theme.palette.text.secondary,
},
}));
const Test = (props) => {
const queueContext = useContext(QueueContext);
const classes = useStyles();
const router = useRouter();
const handle = useFullScreenHandle();
console.log(queueContext);
return (
<QueueWebsocket>
<Row>
{console.log(queueContext)}
</Row>
</QueueWebsocket>
)
}
export default Test;

How to re-render a custom hook after initial render

I have custom hook named useIsUserSubscribed that checks to see a specific user is subscribed. It returns true if the user is subscribed and false if the user is not subscribed...
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { checkSubscription } from "../services";
// this hook checks if the current user is subscribed to a particular user(publisherId)
function useIsUserSubscribed(publisherId) {
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
const currentUserId = useSelector((state) => state.auth.user?.id);
useEffect(() => {
if (!currentUserId || !publisherId) return;
async function fetchCheckSubscriptionData() {
try {
const res = await checkSubscription(publisherId);
setUserIsSubscribed(true);
} catch (err) {
setUserIsSubscribed(false);
}
}
fetchCheckSubscriptionData();
}, [publisherId, currentUserId]);
return userIsSubscribed;
}
export default useIsUserSubscribed;
...I have a button using this hook that renders text conditionally based on the boolean returned from useIsUserSubscribed...
import React, { useEffect, useState } from "react";
import { add, remove } from "../../services";
import useIsUserSubscribed from "../../hooks/useIsUserSubscribed";
const SubscribeUnsubscribeBtn = ({profilePageUserId}) => {
const userIsSubscribed = useIsUserSubscribed(profilePageUserId);
const onClick = async () => {
if (userIsSubscribed) {
// this is an API Call to the backend
await removeSubscription(profilePageUserId);
} else {
// this is an API Call to the backend
await addSubscription(profilePageUserId);
}
// HOW CAN I RERENDER THE HOOK HERE!!!!?
}
return (
<button type="button" className="sub-edit-unsub-btn bsc-button" onClick={onClick}>
{userIsSubscribed ? 'Subscribed' : 'Unsubscribed'}
</button>
);
}
After onClick I would like to rerender my the useIsUserSubscribed hook So that my button text toggles. Can this be done?
you can not use useEffect in your hook for that purpose try this :
hook :
function useIsUserSubscribed() {
const currentUserId = useSelector((state) => state.auth.user?.id);
const checkUser = useCallback(async (publisherId, setUserIsSubscribed) => {
if (!currentUserId || !publisherId) return;
try {
const res = await checkSubscription(publisherId);
setUserIsSubscribed(true);
} catch (err) {
setUserIsSubscribed(false);
}
}, [currentUserId]);
return {checkUser};
}
export default useIsUserSubscribed;
component :
const SubscribeUnsubscribeBtn = ({profilePageUserId}) => {
const [userIsSubscribed,setUserIsSubscribed]=useState(false);
const { checkUser } = useIsUserSubscribed();
useEffect(()=>{
checkUser(profilePageUserId,setUserIsSubscribed)
},[checkUser,profilePageUserId]);
const onClick = async () => {
if (userIsSubscribed) {
// this is an API Call to the backend
await removeSubscription(profilePageUserId);
} else {
// this is an API Call to the backend
await addSubscription(profilePageUserId);
}
// HOW CAN I RERENDER THE HOOK HERE!!!!?
checkUser(profilePageUserId,setUserIsSubscribed)
}
return (
<button type="button" className="sub-edit-unsub-btn bsc-button" onClick={onClick}>
{userIsSubscribed ? 'Subscribed' : 'Unsubscribed'}
</button>
);
}
you can also add some loading state in your hook and return them too so you can check if process is already done or not
Add a dependece on useIsUserSubscribed's useEffect.
hook :
function useIsUserSubscribed(publisherId) {
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
const currentUserId = useSelector((state) => state.auth.user?.id);
// add refresh dependece
const refresh = useSelector((state) => state.auth.refresh);
useEffect(() => {
...
}, [publisherId, currentUserId, refresh]);
...
}
component :
const onClick = async () => {
...
// HOW CAN I RERENDER THE HOOK HERE!!!!?
// when click, you can dispatch a refresh flag.
dispatch(refreshSubState([]))
}
Expose forceUpdate metheod.
hook :
function useIsUserSubscribed(publisherId) {
const [update, setUpdate] = useState({});
const forceUpdate = () => {
setUpdate({});
}
return {userIsSubscribed, forceUpdate};
}
component :
const {userIsSubscribed, forceUpdate} = useIsUserSubscribed(profilePageUserId);
const onClick = async () => {
...
forceUpdate();
}
Here is another solution by user #bitspook
SubscribeUnsubscribeBtn has a dependency on useIsUserSubscribed, but useIsUserSubscribed don't depend on anything from SubscribeUnsubscribeBtn.
Instead, useIsUserSubscribed is keeping a local state. You have a couple of choices here:
Move the state regarding whetehr user is subscribed or not one level up, since you are using Redux, perhaps in Redux.
Communicate to useIsUserSubscribed that you need to change its internal state.
For 1)
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
move this state to Redux store and use it with useSelector.
For 2), return an array of value and callback from the hook, instead of just the value. It will allow you to communicate from component back into the hook.
In useIsUserSubscribed,
return [userIsSubscribed, setUserIsSubscribed];
Then in onClick, you can call setUserIsSubscribed(false), changing the hook's internal state, and re-rendering your component.

Categories