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;
Related
I am trying to use Socket.io client in a Next.js app. But I am facing one problem; when I perform an event socket runs two times. But I am not finding any solutions. Can anyone help me to debug this problem?
I am using React Context to create a socket provider.
socket.context.tsx-
import { createContext, FC, useContext } from "react";
import { io, Socket } from "socket.io-client";
import { getCookie } from "cookies-next";
const token = getCookie("session")
const socket = io(process.env.NEXT_PUBLIC_WS || "http://localhost:5000", {
extraHeaders: {
"authorization": token?.toString() || ""
}
})
//types
interface SocketContextInterface {
socket: Socket
}
const SocketContext = createContext<SocketContextInterface>({ socket });
interface Props {
children: React.ReactNode
}
const SocketProvider: FC<Props> = (props) => {
return (
<SocketContext.Provider value={{ socket }}>
{props.children}
</SocketContext.Provider>
);
};
export const useSocket = () => useContext(SocketContext)
export default SocketProvider;
And then, I am using it in one component-
const { socket } = useSocket();
//Performing create message on onSubmit handler
const onSubmit: SubmitHandler<Inputs> = (submitData) => {
const MessageData = {
conversation: selected,
message: {
text: submitData.message
}
}
socket.emit("createMessage", MessageData);
reset();
}
//And then I am try to listen event
useEffect(() => {
socket.on("createMessage", (data: MessageData) => {
setMessages((prev: MessageData[]) => [...prev, data])
})
}, [socket])
// But this performs two times
Here socket.on perform two times. How can I solve this problem?
I think your createMessag listener is being registered two times in memory, either because of a re-render or StrictMode. Add a clean-up, like so:
useEffect(() => {
const listener = (data: MessageData) => {
setMessages((prev: MessageData[]) => [...prev, data]);
};
socket.on("createMessage", listener);
return () => {
socket.off("createMessage", listener);
};
}, [socket]);
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);
})
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.
The Redux Update operations I made on the client-side in Next JS are not updated in the server store.
Hello. I have a problem. I'm developing an SSR application with Next JS. I have provided the next js link with the next-redux-wrapper. State update operations can be provided. State updates I made on the server-side can be viewed on the client-side. The redux updates I made on the client-side also appear on the client-side, but when I refresh the page, it returns to the previous position. Sample scenario:
Users have addresses. Their addresses can be taken from the DB and printed on the screen. DB updates when I add a new address or delete the old address. Along with it, it is updated in the store on the client-side. So far there is no problem. However, when I refresh the page, for example, if there are 4 addresses before updating and I deleted one, after the refresh, it is printed as 4 addresses again. It continues like this until I get data from the server again.
How can I move the client-side store updates to the server-side without having to make requests to the server over and over again?
store.js
// store.js
import { createStore, applyMiddleware } from 'redux';
import { createWrapper } from "next-redux-wrapper";
import thunkMiddleware from 'redux-thunk'
// ROOT REDUCERS
import rootReducer from "../reducers";
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const store_ = (initialState) => {
return createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
}
const wrapper = createWrapper(store_/*, { debug: true }*/);
export {
wrapper
}
_app.js
// _app.js
const MyApp = ({props, Component, pageProps }) => {
const store = useStore();
if (!store.getState().R_PageSettings.initStore)
{
store.dispatch({
type: HYDRATE,
payload: {
...props.initialState
}
})
}
return (
<>
<Head>
<title>{ variables.meta.title }</title>
</Head>
<Component {...pageProps} />
</>
)
}
const wrappedApp = wrapper.withRedux(MyApp);
export default wrappedApp;
wrappedApp.getInitialProps = async ctx => {
const data = await wrapper.getServerSideProps(
async (req) => {
const { store, ctx } = req;
const reduxStates = store.getState();
let user = reduxStates.R_User.user;
if (!user)
{
const cookies = parseCookies(ctx);
if (cookies.usr && user !== undefined)
{
const getUser = await CustomersController.tokenLoginControl(cookies.usr);
if (getUser && getUser.status)
{
store.dispatch(setUserSSR(getUser.user))
user = getUser.user;
}
else
destroyCookie(ctx, 'usr');
}
}
return {
user
}
}
)(ctx)
return data;
}
action.js
// CONSTANTS
import {
C_User
} from "../constants";
export const setUserSSR = user => {
return {
type: C_User.SET_USER,
payload: {
user
}
}
}
export const setUser = user => dispatch => {
return dispatch({
type: C_User.SET_USER,
payload: {
user
}
})
}
addresspage.js
// addresspage.js
import { connect } from 'react-redux';
import { bindActionCreators } from "redux";
// COMPONENTS
import UserPageLayout from "../UserPagesLayout";
import {
CustomerAddressForm
} from "../../../components";
// CONTROLLERS
import {
CustomersController
} from "../../../controllers";
// ACTIONS
import {
setUser
} from "../../../actions";
const MyAddressPage = connect(({ R_User }) => {
return {
R_User
}
}, dispatch => {
return {
setUser: bindActionCreators(setUser, dispatch)
}
})((props) => {
const addAddressHandle = () => {
props.fullBarOpen(
<CustomerAddressForm confirmHandle={async (address, setLoading) => {
const execute = await CustomersController.addAddress(address);
if (execute.status)
{
await props.setUser(execute.user);
}
else
{
setLoading(false);
}
}}
/>
);
}
return (
<UserPageLayout>
</UserPageLayout>
);
})
export default MyAddressPage;
I am trying to develop a custom hook which seems to be pretty easy but I am getting an error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
This is my hook:
import React, { useState, useEffect } from 'react';
const useInfiniteScroll = (isLastPage: boolean, fetchFn: any) => {
const [pageCount, setPageCount] = useState(0);
const triggerFetchEvents = (): void => {
let response;
setPageCount(() => {
if (!isLastPage) {
response = fetchFn(pageCount + 1, 5, 'latest');
}
return pageCount + 1;
});
return response;
};
useEffect(() => {
triggerFetchEvents();
}, []);
return pageCount;
};
export default useInfiniteScroll;
And the component here I am calling it:
import React, { FC } from 'react';
import { connect } from 'react-redux';
import { fetchEvents } from '../../shared/actions/eventActions';
import { AppState } from '../../shared/types/genericTypes';
import EventModel from '../../shared/models/Event.model';
import EventListPage from '../../components/events/EventListPage';
import useInfiniteScroll from '../../shared/services/triggerInfiniteScroll';
type Props = {
fetchEvents?: any;
isLastPage: boolean;
eventsList?: EventModel[];
};
const mapState: any = (state: AppState, props: Props): Props => ({
eventsList: state.eventReducers.eventsList,
isLastPage: state.eventReducers.isLastPage,
...props
});
const actionCreators = {
fetchEvents
};
export const EventsScene: FC<Props> = props => {
const { eventsList, fetchEvents, isLastPage } = props;
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// const [pageCount, setPageCount] = useState(0);
// const triggerFetchEvents = (): void => {
// let response;
// setPageCount(() => {
// if (!isLastPage) {
// response = fetchEvents(pageCount + 1, 1, 'latest');
// }
// return pageCount + 1;
// });
// return response;
// };
// useEffect(() => {
// triggerFetchEvents();
// }, []);
if (!eventsList || !eventsList.length) return null;
return (
<EventListPage
eventsList={eventsList}
isLastPage={isLastPage}
triggerFetchEvents={useIn}
/>
);
};
export default connect(
mapState,
actionCreators
)(EventsScene);
I left the commented code there to show you that if I uncomment the code and remove useInfiniteScroll then it works properly.
What could I be missing?
UPDATE:
This is EventListPage component
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EventModel from '../../shared/models/Event.model';
import { formatDate } from '../../shared/services/date';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
type Props = {
eventsList?: EventModel[];
isLastPage: boolean;
triggerFetchEvents: any;
};
export const EventListPage: React.FC<Props> = props => {
const { eventsList, triggerFetchEvents, isLastPage } = props;
const [isFetching, setIsFetching] = useState(false);
const fetchMoreEvents = (): Promise<void> =>
triggerFetchEvents().then(() => {
setIsFetching(false);
});
const handleScroll = (): void => {
if (
document.documentElement.offsetHeight -
(window.innerHeight + document.documentElement.scrollTop) >
1 ||
isFetching
) {
return;
}
return setIsFetching(true);
};
useEffect(() => {
if (isFetching) return;
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if (!isFetching) return;
if (!isLastPage) fetchMoreEvents();
}, [isFetching]);
if (!eventsList) return null;
return (
<Container className='article-list mt-5'>
///...
</Container>
);
};
export default EventListPage;
In EventsScene, change useInfiniteScroll to be invoked directly at the function body top-level (not sure why you are creating this indirection in the first place):
// before
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// after
useInfiniteScroll(isLastPage, fetchEvents)
React expects Hook calls to only happen at the top-level as it relies on the order of Hooks to be always the same. If you wrap the Hook in a function, you can potentially invoke this function in many code locations disturbing the Hooks' order.
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state. Link