React app fails to process websocket message under high frequency of messages - reactjs

I have the following code that subscribes to a socket. I have noticed that under high frequency of messages, the display_data state sometimes falls out of sync. This occurs despite the client receiving all the messages in sequential order. Could this happen because React is setting the state in an asynchronous and mutable way? Are multiple messages being processed at the same time?
Would a better model for processing data to put the incoming messages into a client side queue and then process each item in the queue sequentially, removing item after processing, and then update the state? If this is ideal, how can I do this?
const client = new w3cwebsocket('ws://localhost:5424');
export class DataManager extends React.Component {
constructor(props) {
super(props);
this.state = {
display_data: {},
key_count: 0,
pipe_count: 0,
data_schema: data_schema
};
client.onopen = () => {
console.log('WebSocket connected!');
};
}
componentDidMount() {
client.onmessage = (message) => {
this.processIncomingMessage(message.data);
}
}
processIncomingMessage(message) {
let message_json = JSON.parse(message);
let message_type = message_json['type'];
let message_time = message_json['update_time'];
let message_data = message_json['data'];
let total_keys = message_json['total_keys'];
if (message_type === 'sync') {
this.setState({
display_data: message_json['data'],
});
} else {
let data = Object.assign({}, this.state.display_data);
Object.keys(message_data).forEach(index => {
data[message_data[index]['key']] = 1;
});
this.setState({
display_data: data,
});
console.log(message_json['pipe_count'], Object.keys(data).length, total_keys);
}
this.setState({
pipe_count: this.state.pipe_count + 1,
key_count: Object.keys(this.state.display_data).length
});
}
componentWillUnmount() {
client.close();
}
}

Related

How do i integrate pubnub in mobile devices

i am trying to integarte pubnub(replacing socket.io withh pubnub).here below i have shown code.it is working fine for pc.but in mobile devices its not working .i am not getting error as well.any body can tel what i done wrong.
till now i have tried to replace socket with my pubnub connection
import PubNub from 'pubnub';
import { PubNubProvider, usePubNub } from 'pubnub-react';
const pubnub = new PubNub({
publishKey: 'xxxxxxxxxxxx',
subscribeKey: 'xxxxxxxxxxx'
});
//leave Table when close window
const closingCode = () => {
sendMsg("leaveTable");
return null;
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.receiveMsg = []
}
window.onbeforeunload = closingCode;
// Read res from service via Socket IO
// socket.on("message", receiveMsg);
socket.on("message", text => {
let params = text.split("|"); //.map(p => Base64.Decode(p)); // we are not using b64 now
let message = params.shift(); // message, eg. playerSitOut, clearTable
this.receiveMsg.push(message);
this.props.updateMessage({ message, params });
});
pubnub.addListener({ message: function (m) {
const channelName = m.channel; // Channel on which the message was published
const channelGroup = m.subscription; // Channel group or wildcard subscription match (if exists)
const pubTT = m.timetoken; // Publish timetoken
const msg = m.message; // Message payload
const publisher = m.publisher; // Message publisher/ } });
//pubnub.subscribe();
}

can't append to sourceBuffer because mediaSource Element (Parent) was removed

I'm having problem with video Streaming.
I'm using socket io for video Streaming and react for front end.
Front End code looks like this:-
class VideoElement extends React.Component {
constructor(props) {
/* props is having Id, projectDesignSocket */
super();
this.state = { hasError: false };
this.videoRef = React.createRef();
this.mediaSource = null;
this.sourceBuffer = null;
}
componentDidMount() {
this.mediaSource = new MediaSource();
this.videoRef.current.src = URL.createObjectURL(this.mediaSource);
this.mediaSource.onsourceopen = (e) => {
URL.revokeObjectURL(this.videoRef.current.src);
this.sourceBuffer = this.mediaSource.addSourceBuffer("video/webm; codecs=\"vp8, opus\"");
this.sourceBuffer.mode = 'sequence';
this.props.projectDesignSocket.on(`${this.props.Id}VideoData`, ({ blob }) => {
try {
this.sourceBuffer.appendBuffer(blob);
}
catch (err) {
console.log(err);
}
finally {
console.log(this.sourceBuffer);
}
});
}
}
render() {
if (this.state.hasError) {
return <h1>error</h1>;
}
else {
return <video autoPlay ref={this.videoRef} id={this.props.Id}></video>;
}
}
componentWillUnmount() {
this.mediaSource.onsourceopen = null;
this.props.projectDesignSocket.off(`${this.props.Id}VideoData`);
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.log(error, info);
}
}
class VideoConfrence extends React.Component {
/* prop is having projectDesignSocket, isAdmin, userList, currentRoom, userName, userId */
constructor(props) {
super();
this.state = {};
this.userVideoRef = React.createRef();
this.mediaRecorder = null;
this.videoStream = null;
}
static getDerivedStateFromProps(nextProp, prevState) {
if (nextProp.userList !== prevState.userList) {
return { userList: nextProp.userList }
}
else {
return {};
}
}
componentDidMount() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
}).then(stream => {
this.videoStream = stream;
this.userVideoRef.current.srcObject = stream;
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.start(100);
this.mediaRecorder.ondataavailable = (e) => {
if (e.data.size) {
this.props.projectDesignSocket.emit('video', {
room: this.props.currentRoom,
data: { userId: this.props.userId, blob: e.data }
});
}
}
}).catch(err => {
console.log(err);
});
}
render() {
return (
<div className='videoConfrencing'>
<video autoPlay ref={this.userVideoRef} />
{
this.state.userList.map((item, index) => {
if (item !== this.props.userId) {
return <VideoElement key={index} Id={item} projectDesignSocket={this.props.projectDesignSocket} />
}
else {
return null;
}
})
}
</div>
);
}
componentWillUnmount() {
this.videoStream.getTracks().forEach(function (track) {
track.stop();
});
this.mediaRecorder.ondataavailable = null;
this.props.projectDesignSocket.off('newMemberAdded');
}
}
And Backend Like This :-
socket.on('video', function ({ room, data }) {
// data has userid and blob as it's properties
if (room && data ) {
socket.broadcast.to(room).emit(`${data.userId}VideoData`, {blob:data.blob});
}
})
error is occuring at try/catch block of componentDidMount of VideoElement
err:-Media resource blob:http://localhost:3000/493042c5-431b-40a1-aff6-5fafe218a677 could not be decoded, error: Error Code: NS_ERROR_FAILURE (0x80004005)
basicaly i'm designing an app for video confrencing and,
i've used mediaRecorder to record stream obtained by getUserMedia into Blobs and send those blobs to server throgh socket ('video event')
and the server receives roomName from which event was emmited, userId and blob of video and broadcast an event ${userId}VideoData to the specific room
when a user connects the server start receiving blobs and start broadcasting event ${userId}VideoData with {userId, blob} as values to be passed to client
when another user connects his webcam feeds are also sent to server...the user connected before him receives his video blobs and is able to play them , this user also receives video blobs of previously connected user but is unable to play them , on first append to sourceBuffer above warning is logged after which if appended again error is thrown!!
the error is that user which connects cant see the video of other users which were connected before him but is able to see the video of users connected after him!!
but users connected before him are able to see his video without any error

React.js logging before operation is finished

onReaderLoad = (e) => {
var obj =JSON.parse(e.target.result)
this.setState(prevState => ({
datasource: [...prevState.datasource, obj]
}))
}
ReadFiles = () => {
let files = this.state.json_files;
for (let i of files){
var reader = new FileReader();
reader.onload = this.onReaderLoad;
reader.readAsText(i);
}
console.log(this.state.datasource)
}
getfolder = (e) => {
var files = e.target.files;
this.setState({
json_files: files
}, () => this.ReadFiles())
}
<input type="file" onChange={this.getfolder} multiple accept=".json" />
Here i am sharing my code.
What i am trying to do is i am reading all the json files from user input and looping them and storing it to react state.
Then inside ReadFiles() function i am logging the state data. But it is always coming empty data.
I think it calling first and then going to the loop.
I wants to log the datasource data from state inside ReadFiles() function after all Looping operation is done
Is there any way to do that ?
Please have a look
You can make use of instance variable in this case to keep track of whether you are processing last file or not. Once you know that last file is being processed, you can set doNextStep to true in state from callback of last processed file. Then in componentDidUpdate, you can do next step(i.e. console log or anything else).
class Example extends React.Component {
constructor(props){
super(props);
// to keep track of if last file is being processed
this.lastFile = false;
// doNextStep will tell us that all setState operations are completed or not
this.state = {
doNextStep: false,
}
}
componentDidUpdate(prevProps, prevState) {
if(prevState.doNextStep !== this.state.doNextStep && this.state.doNextStep) {
console.log(this.state.datasource)
}
}
onReaderLoad = (e) => {
var obj =JSON.parse(e.target.result)
this.setState(prevState => ({
datasource: [...prevState.datasource, obj]
}),
() => if(this.lastFile)) this.setState({doNextStep: true}))
}
ReadFiles = () => {
let files = this.state.json_files;
for (i=0; i<files.length; i++){
if(i == files.length - 1) this.lastFile = true
else this.lastFile = false
var reader = new FileReader();
reader.onload = this.onReaderLoad;
reader.readAsText(i);
}
}
}

How to use a method in render reactjs?

i have a method set_data which is used to set data based on id. I know it could be easy to call this set_data in componentdidupdate when id changes. However in doing so it doesnt set some state variables in the parent component.
To get rid of that want to call set_data method in render . However since this set_data method sets state of data it enters into an infinite loop in render . Also cannot provide a condition (like prevprops.id!== this.props.id) to execute set_data method.
To prevent it thought of using this set_data method not to set state at all. and can call this set_data method in render.
Below is the code,
export default class child extends React.Component {
state = {
query: '',
data: null,
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.set_data();
}
}
set_data = () => {
if (!this.props.info) {
return;
}
if (this.props.id === this.empty_id) {
this.setState({data: null});
return;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({data: this.filter_data(data, this.state.query)});
};
render = () => {
const shown_data= this.state.data;
/* i want to call set_data method here*/};}
Could someone help me solve this. Thanks.
You can't call setData there, because that would be anti-pattern. It will trigger a loop that will continuously render as well as keeps setting state.
You can probably rewrite the component this way:
export default class child extends React.Component {
state = {
query: ''
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
set_data = () => {
let data = {};
if (!this.props.info) {
return data;
}
if (this.props.id === this.empty_id) {
return data;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
data = this.filter_data(data, this.state.query);
return data;
};
render = () => {
const shown_data= this.state.data;
const data = this.set_data();
/* i want to call set_data method here*/};}
In this, we are not setting data in the state. For every new ID, it will get new data and will compute it from render thereby avoiding antipattern. I have also removed componentDidMount, since we are doing computation in render. Note: This solution means taking away data from the state, if you are not using data anywhere before render, this will work.
Let me know if this helps.

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Resources