Reactjs not re-rendering update received from WebSockets - reactjs

I am using WebSockets to update upvotes on comments in React. I am receiving comment updates in logs of different client instances. However, React does not render the updates to upvotes.
Code I am trying:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
class Comment extends Component {
constructor(props){
super(props);
this.upvotes = React.createRef();
this.downvotes = React.createRef();
this.handleUpvote = this.handleUpvote.bind(this);
this.handleDownvote = this.handleDownvote.bind(this);
}
handleUpvote(){
console.log(this.props);
const json = { type: 'upvote' };
json.data = this.props;
json.data.comment.upvotes++;
console.log(json);
this.props.socket.send(JSON.stringify(json));
}
handleDownvote(){
this.downvotes.current.innerHTML++;
console.log(this.downvotes.current.innerHTML);
}
render() {
return (
<tr>
<td>{this.props.comment.user.firstName} {this.props.comment.user.lastName}</td>
<td>{this.props.comment.content }</td>
<td> <span ref={this.upvotes}>{this.props.comment.upvotes}</span> <button onClick={this.handleUpvote}>Upvote</button> </td>
<td> <span ref={this.downvotes}>{this.props.comment.downvotes}</span> <button onClick={this.handleDownvote}>Downvote</button> </td>
</tr>
)
}
}
export default class ListComments extends Component {
constructor(props){
super(props);
this.state = { comments: [] }
}
componentDidMount(){
axios.get('http://localhost:5000/api/comments/')
.then(resp => this.setState({ comments : resp.data }))
.catch(err => console.log(err));
}
componentWillReceiveProps(nextProps){
const data = JSON.parse(nextProps.comment);
console.log(data.data);
if(data.type === "upvote"){
// const a = this.state.comments;
// a.forEach(comment => {
// if(comment._id == data.data.comment._id){
// comment = data.data.comment
// }
// });
// this.setState({ comments : a })
this.setState(prevState => {
// Get previous state
const { comments } = prevState;
// Add new item to array
comments.forEach(comm => {
if(comm._id == data.data.comment._id){
comm = data.data.comment
}
});
// Return new state
return { comments };
});
}
else if(data.type === "comment"){
this.setState({ comments : [data.data, ...this.state.comments] })
}
}
commentList() {
return this.state.comments.map(currentcomment => {
return <Comment comment={currentcomment} socket={this.props.actions} key={currentcomment._id}/>;
})
}
render() {
return (
<div>
<h3>Comments</h3>
<table className="table">
<thead className="thead-light">
<tr>
<th>Username</th>
<th>Content</th>
<th>Upvotes</th>
<th>Downvotes</th>
</tr>
</thead>
<tbody>
{ this.commentList() }
</tbody>
</table>
</div>
);
}
}
Outputs I am getting -
Client one with 3 upvotes to question 1
Client 2 with updates to upvotes received in console, not rendred in actual comment

Related

React js TypeError: Cannot read properties of undefined (reading 'setState')

So i have problem with setting state for class component while using websockets.
picture of problem
it's seems like my backend works fine and problem is on the frontend side.
I'm new to react so i don't know how the fix the problem here.
and data 'jsonBody' is delivering the right information we need aswell. so i guess the only problem is with for setting state for this.state.users list.
frontend code:
class Users extends React.Component {
// Constructor
constructor(props) {
super(props);
this.state = {
users: []
}
this.deleteUsers = this.deleteUsers.bind(this);
this.editUsers = this.editUsers.bind(this);
this.addUsers = this.addUsers.bind(this);
}
componentDidMount(){
fetch("http://localhost:8080/users")
.then(response => {
return response.json()
})
.then(data => {
this.setState({users: data})
})
const SOCKET_URL = 'ws://localhost:8080/ws-message';
let onConnected = () => {
console.log("Connected!!")
client.subscribe('/topic/message', function (msg) {
if (msg.body) {
var jsonBody = JSON.parse(msg.body);
this.setState({users: this.state.users.concat(jsonBody) });
}
});
}
let onDisconnected = () => {
console.log("Disconnected!!")
}
const client = new Client({
brokerURL: SOCKET_URL,
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
onConnect: onConnected,
onDisconnect: onDisconnected
});
client.activate();
}
addUsers(user) {
this.setState({users: this.state.users.concat(user) })
}
editUsers(user) {
this.setState({users: this.state.users.map((users) => users.id === user.id ? user : users)})
}
deleteUsers(id){
const api = "http://localhost:8080/users"
axios.delete(api + "/" + id).then( res => {
this.setState({users: this.state.users.filter(user => user.id !== id)});
});
}
render() {
return (
<div className="container">
<h1>Manage users</h1>
<table className="table">
<thead>
<tr>
<th>name</th>
<th>username</th>
<th>email</th>
<th></th>
</tr>
</thead>
<tbody>
{this.state.users.map(user =>
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td className="d-flex justify-content-end" ><Example user={user} users={this.state.users} editUsers={this.editUsers} ></Example>
<button className="remove btn btn-danger btn-sm ms-3" onClick={ () => this.deleteUsers(user.id)}>Remove</button>
</td>
</tr>
)}
</tbody>
</table>
<Example users={this.state.users} addUsers={this.addUsers} />
</div >
);
}
}
export default Users;
backend:
#PostMapping("/users")
public List<User> addUsers(#RequestBody User user){
UserDao.addUser(user);
template.convertAndSend("/topic/message" ,user);
return UserDao.showPostedUser();
}
#PutMapping("/users/{id}")
public ResponseEntity<HttpStatus> editUsers(#PathVariable int id,#RequestBody User user){
UserDao.updateUser(user,id);
template.convertAndSend("/topic/update", UserDao.showUpdatedUser(id));
return ResponseEntity.ok(HttpStatus.OK);
}
#DeleteMapping("/users/{id}")
public ResponseEntity<HttpStatus> deleteUsersById(#PathVariable int id){
UserDao.deleteUserById(id);
template.convertAndSend("/topic/delete", id);
return ResponseEntity.ok(HttpStatus.OK);
}
#SendTo("/topic/message")
public User broadcastMessage(#Payload User user) {
return user;
}
#SendTo("/topic/delete")
public int broadcastDelete(#Payload int id) {
return id;
}
#SendTo("/topic/update")
public User broadcastUpdate(#Payload User user) {
return user;
}
}
Arrow functions and functions are not interchangeable. You lose lexical this of the component when using a function.
client.subscribe('/topic/message', (msg) => {
if (msg.body) {
var jsonBody = JSON.parse(msg.body);
this.setState({users: this.state.users.concat(jsonBody) });
}
});

Maximum update depth exceeded. there is no direct function call in render method

i check the other SO threads about this error, they says that you have some function calling in render method. but here i don't have any.
it's automatically calling componentDidMount. i don't know,which part is calling it. please help
my component
class TeacherDashboard extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
questionPapers: ''
};
}
componentDidMount() {
this.props.removeError();
apiCall('get', `${process.env.REACT_APP_BASE_URL}/api/questionpapers`, undefined) //fetch all question set
.then(data => {
if (!data.success) {
throw Error(data.message);
}
else {
this.setState({
isLoading: false,
questionPapers: data.questionPapers
})
}
})
.catch(err => {
this.setState({
isLoading: false
})
this.props.addError(err.message || 'something went wrong. please try again later.')
});
}
deleteQuestionPaper = (questionPaperId) => {
apiCall('delete', `${process.env.REACT_APP_BASE_URL}/api/questionpapers/${questionPaperId}`, undefined)
.then(data => {
if (!data.success) {
throw Error(data.message);
}
else {
this.setState({
questionPapers: this.state.questionPapers.filter((questionPaper) => questionPaper._id !== questionPaperId)
})
}
})
.catch(err => {
this.props.addError(err.message || 'something went wrong. please try again later.')
});
}
render() {
debugger
let { isLoading, questionPapers } = this.state;
let dashboard = questionPapers ? questionPapers.length > 0 ?
<QuestionPaperInfo questionPapers={questionPapers} deleteQuestionPaper={this.deleteQuestionPaper} /> :
<div className='bg-danger h2 p-1' >No question paper found. please create one.</div> :
null;
return (
<div className='mt-2'>
{isLoading ? <p className='h1'>Loading......</p> : dashboard}
<Link to='/teachers/newquestionpaper' className='btn btn-warning'>Add a new Question paper</Link>
</div>
)
}
}
export default TeacherDashboard;
QuestionPaperInfo component
const QuestionPaperInfo = (props) => {
return (
<table className="table table-hover text-center">
<thead className="thead-dark">
<tr>
<th scope="col">S.N.</th>
<th scope="col">Subject</th>
<th scope="col">Class</th>
<th scope="col">Total Questions</th>
<th scope="col">Total Marks</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{props.questionPapers.map((questionPaper,i)=>{
return <tr key={questionPaper._id||i}>
<th scope='row'> {i+1}</th>
<th><Link to={`/teachers/${questionPaper._id}`}>{questionPaper.subject}</Link></th>
<th>{questionPaper.standard}</th>
<th>{questionPaper.totalQuestions}</th>
<th>{questionPaper.totalMarks}</th>
<th className='text-danger' onClick={()=>props.deleteQuestionPaper.bind(null, questionPaper._id )}>Delete</th>
</tr>
})}
</tbody>
</table>
)
}
export default QuestionPaperInfo
parent component
import React, { Component } from 'react';
import { withRouter } from "react-router-dom"
const withAuth = (ComponentToBeRendered)=>{
class Authenticate extends Component {
componentWillMount() {
if (!window.localStorage.jwtToken) {
debugger
// console.log(this.props)
this.props.addError('please signin first.');
this.props.history.push('/auth/signin');
}
}
componentWillUpdate(nextProps) {
if (!window.localStorage.jwtToken) {
this.props.addError('please signin first.');
this.props.history.push('/auth/signin');
}
}
render() {
return <ComponentToBeRendered removeError={this.props.removeError} addError={this.props.addError} />
}
}
return withRouter(Authenticate)
}
export default withAuth;
inside app.js
render(){
let WithHocTeacherDashboard = withAuth(TeacherDashboard);
return
<Route exact path='/teachers/me' render={props=> <WithHocTeacherDashboard addError={this.addError} removeError={this.removeError} />} />
}
it's failing at addError method in app.js
Seems like your trying to setState in componentDidMountwithout setting a condition so basically every update runs the component again.
try entering logic of componentDidMount inside a condition comparing old props with new props.
like this
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
// your logic
}
}
ou may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance.
componentDidMount()

How to get updated data in React

How to get updated data of react by calling the new data that will be received from another page by ajax?
How to replace new data to "Result" div.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
data: [],
}
$.ajax({
url:"/test.bc",
type:"get",
success:(result)=>{
this.setState({data: eval(result)});
}
})
$(document).on('update_result',(event,startairline,classname,stops)=>{
$.ajax({
url:"/new.bc",
type:"post",
data:{
startairline:startairline,
stops:stops,
classname:classname,
},
success:(result)=>{
console.log(result)
this.setState({hasBeenChanged: true,data:eval(result)})
},
})
});
}
renderFlight(){
return this.state.data.map((item)=>{
return(<input type="hidden" value={item.total} name="total" /> )
} )}
render(){
return(<div>{this.renderFlight()}</div> )
}
}
ReactDOM.render(<App/>, document.getElementById('Result'));
I prepare you an example, using componentDidMount and fetch:
Here working
let { Table } = ReactBootstrap;
class Example extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
products: []
}
}
componentDidMount() {
console.log('componentDidMount..')
fetch('https://api.github.com/users/xiaotian/repos')
.then(response => response.json())
.then(output => {
let products = []
for (let i = 0; i < output.length; i++) {
products.push({selected:false,name:output[i].name})
}
this.setState({products},() => console.log(this.state))
})
}
render() {
return(<Table striped bordered condensed hover>
<thead>
<tr>
<th>Selected</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{this.state.products.map((item, i) => {
return (
<tr><td><input type="checkbox" checked={item.selected}/></td><td>{item.name}</td></tr>
)
})}
</tbody>
</Table>)
}
}
ReactDOM.render(
<Example />,
document.getElementById('app')
);

How to correctly bind React onClick event with Redux?

Basically there's no sign that the event is binded somewhere and it's not firing. Here's the Component
class AgendaPointsList extends React.Component {
constructor(props) {
super(props);
this.onAgendaPointClick = this.props.onAgendaPointClick.bind(this);
}
render() {
let items = this.props.agenda_points.map((a, i) => {
return <AgendaPoint key={i} agenda_point={a} index={i} onClick={this.onAgendaPointClick} />
})
console.log(this.props)
return (
<table>
<tbody>
{items}
</tbody>
</table>
);
}
}
The console.log(this.props) outputs:
Object
item_point: Object
item_points: Array[4]
onItemPointClick: onItemPointClick(id)
onModalCloseClick: onModalCloseClick(id)
store: Object
storeSubscription: Subscription
__proto__: Object
Here's the redux component:
const OPEN_AGENDA_POINT = 'meeting/OPEN_AGENDA_POINT'
const CLOSE_AGENDA_POINT = 'meeting/CLOSE_AGENDA_POINT'
const initialState = {
modal_is_open: false,
point_id: 0,
point_data: {}
}
const openAgendaPoint = function (id) {
return {
type: OPEN_AGENDA_POINT,
id: id
}
}
const closeAgendaPoint = function (id) {
return {
type: CLOSE_AGENDA_POINT,
id: id
}
}
const agendaPointsReducer = function (state = initialState, action) {
switch (action.type) {
case OPEN_AGENDA_POINT: {
state.modal_is_open = true,
point_id = action.id
}
case CLOSE_AGENDA_POINT: {
state.modal_is_open = false
}
default:
return state
}
}
const agendaPointsUiStateProps = (state) => {
return {
agenda_point: state.point_data
}
}
const agendaPointsUiActions = (dispatch) => {
return {
onAgendaPointClick: (id) => {
console.log(id)
dispatch(openAgendaPoint(id))
},
onModalCloseClick: (id) => {
dispatch(closeAgendaPoint(id))
}
}
}
const store = Redux.createStore(
agendaPointsReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
// Usage:
const AgendaPointsList = connectWithStore(
store,
AgendaPointsList,
agendaPointsUiStateProps,
agendaPointsUiActions
)
That's the child component:
class AgendaPoint extends React.Component {
render() {
return (
<tr>
<td>{ this.props.index + 1 }</td>
<td>{ this.props.agenda_point.title}</td>
<td>6</td>
<td>{ this.props.agenda_point.agenda_time } min</td>
</tr>
);
}
}
I tried multiple ways of binding the event:
onClick={this.props.onAgendaPointClick.bind(a.id, this)
onClick={this.props.onAgendaPointClick(a.id, this).bind(this)
onClick={() => this.props.onAgendaPointClick(a.id))
Non seem to work.
Using this for reac-redux connect wrapper to pass in store. This is running on Ruby on Rails Sprockets beta4.
What is the correct way of doing this?
You want the on click to be on you tag.
With the following code change you event will be triggerd:
class AgendaPoint extends React.Component { render() {
return (
<tr onClick={this.props.onClick}>
<td>{ this.props.index + 1 }</td>
<td>{ this.props.agenda_point.title}</td>
<td>6</td>
<td>{ this.props.agenda_point.agenda_time } min</td>
</tr>
); } }
Try binding the event in your ItemList constructor:
constructor(props) {
super(props);
this.onItemClick = this.onItemClick.bind(this);
}
Then in your ItemList render function ...
let items = this.props.agenda_points.map((a, i) => {
return <Item key={i} agenda_point={a} index={i} onClick={this.props.onItemClick} />
})
This assumes that the onItemClick function is defined in ItemList parent, and is being passed in as a prop.

React Drag n Drop PUT request after props

I am building an application that requires a table of items to be sorted and change the orderNumber of them depending on their sorting. I installed and utilized a library called react-dnd to handle the functionality of sorting/ordering, and its working great so far. The issue im having is the update. When a user moves one of the items, I need to send a PUT request to the api and update its orderNumber. It was working last night great, here is my code.
The ListItem (Item that is being sorted and updated):
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {DragSource, DropTarget} from 'react-dnd';
import sdk from '../../js/sdk';
import ItemTypes from './ItemTypes';
const itemSource = {
beginDrag(props) {
return {id: props.id};
}
};
const itemTarget = {
hover(props, monitor) {
const draggedId = monitor.getItem().id;
if (draggedId !== props.id) {
props.swapItems(draggedId, props.id);
}
}
};
const DragSourceDecorator = DragSource(ItemTypes.ITEM, itemSource, (connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
};
});
const DropTargetDecorator = DropTarget(ItemTypes.ITEM, itemTarget, (connect) => {
return {connectDropTarget: connect.dropTarget()};
});
class SwagBagItem extends React.Component {
constructor(props) {
super(props);
this._handleDelete = this._handleDelete.bind(this);
}
componentWillReceiveProps(nextProps) {
const swagbagItemCpy = Object.assign({}, nextProps.swagbagItem);
delete swagbagItemCpy.id;
if (nextProps) {
sdk.put(`swagbags/${nextProps.swagbag.id}/items/${nextProps.swagbagItem.id}`, swagbagItemCpy)
.done((result) => {
console.log(result);
}).fail((error) => {
console.log(error);
})
;
}
}
_handleDelete(event) {
event.preventDefault();
event.stopPropagation();
if (confirm('Are you sure you want to delete this Swagbag Item?')) {
sdk.delete(`swagbags/${this.props.swagbag.id}/items/${this.props.swagbagItem.id}`)
.done(() => {
console.log('Swagbag Item remove!');
}).then(() => {
this.props.loadSwagBags();
});
}
}
render() {
const {swagbagItem} = this.props;
return this.props.connectDragSource(this.props.connectDropTarget(
<tr className="swagbag-item">
<td>{swagbagItem.id}</td>
<td><Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}`}>{swagbagItem.name}</Link></td>
<td>{swagbagItem.uri}</td>
<td>
<div className="btn-group btn-group-xs pull-right" role="group">
<Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}/edit`} className="btn btn-info">Edit</Link>
<Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}`} className="btn btn-info">View</Link>
<button className="btn btn-danger btn-xs" onClick={this._handleDelete}>Remove</button>
</div>
</td>
</tr>
));
}
}
SwagBagItem.propTypes = {
loadSwagBags: PropTypes.func,
params: PropTypes.object,
swagbag: PropTypes.object,
swagbagItem: PropTypes.object,
};
export default DropTargetDecorator(DragSourceDecorator(SwagBagItem));
The container or list that holds these items:
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import sdk from '../../js/sdk';
import Nav from '../Nav';
import SwagBagItem from '../SwagBagItem';
class SwagBagItemsList extends React.Component {
constructor(props) {
super(props);
this.state = {
swagbag: null,
swagbagItems: [],
};
this._loadSwagBags = this._loadSwagBags.bind(this);
this._compareItems = this._compareItems.bind(this);
this._swapItems = this._swapItems.bind(this);
}
componentWillMount() {
this._loadSwagBags();
}
_compareItems(item1, item2) {
return item1.orderNumber - item2.orderNumber;
}
_swapItems(itemNo1, itemNo2) {
const items = this.state.swagbagItems;
let item1 = items.filter(item => item.id === itemNo1)[0];
let item2 = items.filter(item => item.id === itemNo2)[0];
let item1Order = item1.orderNumber;
item1.orderNumber = item2.orderNumber;
item2.orderNumber = item1Order;
items.sort(this._compareItems);
this.setState({swagbagItems: items});
}
_loadSwagBags() {
sdk.getJSON(`swagbags/${this.props.params.id}`)
.done((result) => {
this.setState({swagbag: result});
})
.then(() => {
sdk.getJSON(`swagbags/${this.props.params.id}/items?fields=id,name,summary,uri,itemImageFile,orderNumber`).done((results) => {
this.setState({swagbagItems: results});
});
});
}
render() {
let swagbagItems = null;
if (this.state.swagbagItems) {
swagbagItems = this.state.swagbagItems.map((item) => {
return <SwagBagItem
loadSwagBags={this._loadSwagBags}
swagbag={this.state.swagbag}
swagbagItem={item}
key={item.id}
id={item.id}
swapItems={this._swapItems}
/>;
});
}
if (!this.state.swagbag) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Swagbag Items</h1>
<Nav swagbag={this.state.swagbag} />
<table className="table">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>uri</th>
<th></th>
</tr>
</thead>
<tbody>
{swagbagItems}
</tbody>
</table>
<Link to={`swagbags/createItem/swagbagid/${this.state.swagbag.id}`} className="btn btn-success">Add Item</Link>
</div>
);
}
}
SwagBagItemsList.propTypes = {
params: PropTypes.object,
};
export default DragDropContext(HTML5Backend)(SwagBagItemsList);
It is making the PUT request, but its making hundreds of them in a row from just moving one object. I cant for the life of me figure out why. This puts a severe lag on the application and makes it unresponsive. Am I going about this the right way, and if so, what is the solution to this?
EDIT #1: Woke up today and the application is working fine. Unfortunately this is going in production, so before that I have to recreate the bug of 800+ PUT requests and figure it out. Might put a bounty on this.
If you want to get it so that it sends the update once it's finished dragging, there's an endDrag function you can add to your DragSource (http://gaearon.github.io/react-dnd/docs-drag-source.html) that will only be fired once and will only be fired upon finishing the drag. So if you remove your api call from componentWillReceiveProps and move it to the source like this:
const itemSource = {
beginDrag(props) {
return {
id: props.id,
swagbagId: props.swagbag.id,
swagbagItem: props.swagbagItem,
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
sdk.put(`swagbags/${item.swagbagId}/items/${item.swagbagItem.id}`, item.swagbagItem)
.done((result) => {
console.log(result);
}).fail((error) => {
console.log(error);
})
;
},
};
It should only make the call one time (I can't perfectly predict that without knowing what's in swagbag and swagbagItem but I think it should). Note that I'm using the getItem() function from the DragSource monitor (http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html) to retrieve what was passed in upon beginDrag.

Resources