`componentDidUpdate()` not triggering on a different tab/screen - node.js

I am developing a small game (similar to tictactoe) in React which uses socket.io. He is a description of a scenario were a player makes a move:
Move is sent to the server by emitting a game-move event (from the react client).
Server updates local board array then emits an event containing a copy of the board to the clients(s) via a ${gameId}-board event (clients listen based on the gameId they have).
Upon, the client(s), receiving the event, they update the board with the move played before step one and this reflects on the screen.
The issue is I only see the changes on the screen of the player who played the move and wanted to updated all clients that are in that game (subscribed to the gameId). In other words, updates are only occurring if either person makes a move.
I am using componentDidUpdate() which uses a socket connection to receive events and update the state but for some reason the problem I mentioned above is happening and would appreciate some input. Below is the code for what I just described (minus the server and other components as those are working fine):
class Board extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
squares: Array(6).fill([]).map(() => Array(7).fill(null)),
errorOccured: false,
errorMessage: "",
gameId: "",
};
}
componentDidMount(): void {
...
}
componentDidUpdate(): void {
// #NOTE: This is not triggering on a different tab/screen
socketConnection.on(`${this.state.gameId}-board`, (message) => {
const data = JSON.parse(message);
this.handleMessage(data);
});
}
handleMessage(data: any) {
if(data && data.errorOccured) {
this.setState({errorOccured: true, errorMessage: data.errorMessage});
} else if(data && !data.errorOccured) {
let updatedBoard = data.game.gameState
this.setState({
board: updatedBoard
});
} else {
this.setState({errorOccured: true, errorMessage: "An error occurred, please try again."});
}
}
handleMove(column: number) {
let isValid = this.isMoveValid(column);
if(!isValid) {
this.setState({
errorOccured: true,
errorMessage: "Invalid Move!"
})
} else {
socketConnection.emit(`game-move`,
...
);
}
}
renderSquare(row: number, col: number, key: number) {
return (
<Square
key = { key }
value = { this.state.squares[row][col] }
onClick = { () => this.handleMove(col) }
/>
);
}
render() {
const status = "Current Player: " + ( this.state.xIsNext ? '👻' : '🤡' );
let square = 0;
return (
<div>
<div className="status">{status}</div>
{
this.state.squares.map((row: any, rowIndex: number) =>
(
<div className="row" key={rowIndex} >
{
(row.map((value: any, colIndex: number) => (
this.renderSquare(rowIndex, colIndex, square++)
)))
}
</div>
)
)
}
</div>
)
}
}
export default Board;
Please don't worry/focus about the design, I am simply using this tiny project as an introduction to React.

Related

Push notification shows object Object even though i am setting the right value

I am trying to implement push notifications with react and nodejs using service workers.
I am having problem while i am showing notification to the user.
Here is my service worker code:
self.addEventListener('push', async (event) => {
const {
type,
title,
body,
data: { redirectUrl },
} = event.data.json()
if (type === 'NEW_MESSAGE') {
try {
// Get all opened windows that service worker controls.
event.waitUntil(
self.clients.matchAll().then((clients) => {
// Get windows matching the url of the message's coming address.
const filteredClients = clients.filter((client) => client.url.includes(redirectUrl))
// If user's not on the same window as the message's coming address or if it window exists but it's, hidden send notification.
if (
filteredClients.length === 0 ||
(filteredClients.length > 0 &&
filteredClients.every((client) => client.visibilityState === 'hidden'))
) {
self.registration.showNotification({
title,
options: { body },
})
}
}),
)
} catch (error) {
console.error('Error while fetching clients:', error.message)
}
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
console.log(event)
if (event.action === 'NEW_MESSAGE') {
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (clients.openWindow) {
clients
.openWindow(event.notification.data.redirectUrl)
.then((client) => (client ? client.focus() : null))
}
}),
)
}
})
When new notification comes from backend with a type of 'NEW_MESSAGE', i get the right values out of e.data and try to use them on showNotification function but it seems like something is not working out properly because notification looks like this even though event.data equals to this => type = 'NEW_MESSAGE', title: 'New Message', body: , data: { redirectUrl: }
Here is how notification looks:
Thanks for your help in advance.
The problem was i assigned parameters in the wrong way.
It should've been like this:
self.registration.showNotification(title, { body })

Channel not being connected when trying to enable one on one chat in Stream (JS)

I have an app I'm building where one user (called admin) has a one on one channel with most users. My understanding is that only one channel will exist when I create a one on one chat, regardless of who (admin or user) creates it first. My issue is that when I add a user and a new one on one chat should be established, other channels are being duplicated in my channel list as well. Either that, or sometimes the channel just doesn't connect at all, and the user is left without a chat. The code/pseudo-code I am using is below, does anyone know how to create multiple one on one chats using stream, or why certain channels might be connecting twice?
P.S. I'm editing the code to simplify it to only relevant parts here on stack overflow, so please ignore any simple syntax errors below. Thanks!
setupUserChats = async (chatClient) => {
let { currentUser, participants } = this.props;
if (the current user is an admin) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (if the participant is a user) {
let conversation = await chatClient.channel('messaging', {
name: participant.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
if (currentUser is a user) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (participant is an admin) {
let conversation = await chatClient.channel('messaging', {
name: currentUser.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
this.setState({ chatClient: chatClient });
}
I call another function (outlined below) to set up the basic chat, and then call the above code inside of this next function:
setupChat = async () => {
let { currentUser, participants } = this.props;
let chatClient = await new StreamChat(process.env.REACT_APP_STREAM_API_KEY);
let serverResult = await getStreamToken(currentUser.participant_id);
let userToken = serverResult.token;
await chatClient.setUser(
{
id: currentUser.participant_id,
name: currentUser.name,
},
userToken,
);
const moderatorToAdmin = await chatClient.channel('messaging', `conference-ID`, {
name: "moderator-chat"
});
await moderatorToAdmin.watch();
this.setupUserChats(chatClient);
moderatorToAdmin.addMembers([`${currentUser.participant_id}`]);
this.setState({ chatClient: chatClient });
};
And then its all rendered here:
render() {
const { currentUser } = this.props;
const filters = { members: { $in: [currentUser.participant_id] } }
return (
<div>
<Chat client={this.state.chatClient} theme={'messaging light'} >
<div className="title">Chat</div>
<ChannelList
filters={filters}
Preview={MyChannelPreview}
/>
<Channel Message={MyMessageComponent} >
<Window>
<MessageList />
<MessageInput />
</Window>
<Thread />
</Channel>
</Chat>
</div>
);
}
class MyChannelPreview extends React.Component {
render() {
const { setActiveChannel, channel } = this.props;
return (
<div className={`channel_preview` + currentChannelClass}>
<a href="#" onClick={(e) => setActiveChannel(channel, e)}>
{channel._data.name || channel.data.name}
</a>
</div>
);
}
}

React component not rendering after state change

As the title says, when my state changes in my component, the sub components aren't rerendering.
class App extends Component {
constructor() {
super()
this.state = {
url: ""
}
this.handleWorkerSelect = this.handleWorkerSelect.bind(this)
}
handleWorkerSelect(url) {
this.setState({ url })
}
render() {
return (
<div className="App">
<Workers className="workers" handleClick={this.handleWorkerSelect}/>
<HermesWorker url={this.state.url}/>
</div>
)
}
}
const Workers = (props) => {
return (
<div>
<button onClick={() => props.handleClick("http://localhost:5000/api")}>Worker 1</button>
<button onClick={() => props.handleClick("http://localhost:2000/api")}>Worker 2</button>
</div>
)
}
export default App
here is hermesworker.js
class HermesWorker extends Component {
constructor() {
super()
this.state = {
items: [],
visited: [{name: "This Drive", path: "#back", root: ""}]
}
this.handleFolderClick = this.handleFolderClick.bind(this)
this.handleFileClick = this.handleFileClick.bind(this)
}
componentDidMount() {
if (this.props.url.length === 0) return
fetch(this.props.url)
.then(res => res.json())
.then(items => this.setState({ items }))
}
render() {
const folders = this.state.items.map((item) => {
if (!item.isfile) {
return <Card handleClick={this.handleFolderClick} root={item.root} path={item.path} isfile={item.isfile} name={item.name} size={item.size}/>
}
})
const files = this.state.items.map((item) => {
if (item.isfile) {
return <Card handleClick={this.handleFileClick} root={item.root} path={item.path} isfile={item.isfile} name={item.name} s ize={item.size}/>
}
})
const pathButtons = this.state.visited.map((item) => {
return <PathButton handleClick={this.handleFolderClick} root={item.root} path={item.path} name={item.name}/>
})
return (
<div>
{pathButtons}
<div className="flex-container">
{folders}
{files}
</div>
</div>
)
}
}
Essentially the issue is that the HermesWorker component is not being rerendered to use the new url prop. I am not sure why this is happening because for example, in the hermesworker it renders other subcomponents that do get rerendered during a state change.
Any information is appreciated
EDIT updated to add hermes worker, the file is over 100 lines so i cut out and only pasted the stuff I thought was important to the issue, can supply more if needed
I tested that code and it seems to be working fine. Could you provide What is set in HermesWorker component?
Edit: You'll require to set your state with setState on component updates. To do this, you may look for componentDidUpdate, which will run on every update. This is different from componentDidMount, which (hopefully) will run once and then the component may update and re-render, but re-render it's not considered as "mount". So you may try this instead:
constructor(props) {
super(props);
this.state = {
url: '',
items: [],
visited: [{name: "This Drive", path: "#back", root: ""}]
}
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
//Mount Once
}
componentDidUpdate(prevProps, prevState) {
if (this.state.url !== this.props.url) {
this.setState({url: this.props.url});
// Url state has changed.
}
if(prevState.url !== this.state.url){
//run your fetch
this.fetchData();
}
}
fetchData(){
if (this.props.url.length === 0) return
fetch(this.props.url)
.then(res => res.json())
.then(items => this.setState({ items }));
}
Note: I moved the fetch to its own function, but that's completly up to you.
Also notice i added url to the state. Make sure to keep your props set to avoid unexpected behaviours.
Edit 2: componentDidUpdate will hand you prevProps and prevState as parameters. With prevProps you get access to whatever props you got on the previous update, and with prevState, as you may guess, you get access to whatever-your-state-was on the previous update. And by "on the previous update" i mean before the update got executed.

how can I pass data like the name of my user and put it in the post they created

so I am making an application for events and for some reason when a user creates an event the even info shows but the user info like their name and photo doesn't show up please help I've been having this problem for almost a week now.
THIS IS THE componentDidMount function
async componentDidMount() {
const { data } = await getCategories();
const categories = [{ _id: "", name: "All Categories" }, ...data];
const { data: events } = await getEvents();
this.setState({ events, categories });
console.log(events);
}
THIS IS THE STATE
class Events extends Component {
state = {
events: [],
user: getUser(),
users: getUsers(),
showDetails: false,
shownEventID: 0,
showUserProfile: false,
shownUserID: 0,
searchQuery: ""
};
THIS IS THE EVENTS FILE WHERE THE USER'S NAME AND PHOTO SHOULD BE DISPLAYED
<Link>
<img
className="profilePic mr-2"
src={"/images/" + event.hostPicture}
alt=""
onClick={() => this.handleShowUserProfile(event.userId)}
/>
</Link>
<Link style={{ textDecoration: "none", color: "black" }}>
<h4
onClick={() => this.handleShowUserProfile(event.userId)}
className="host-name"
>
{getUser(event.userId).name}
</h4>
</Link>
This is the userService file where the getUser function is
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/users";
export function register(user) {
return http.post(apiEndPoint, {
email: user.email,
password: user.password,
name: user.name
});
}
export function getUsers() {
return http.get(apiEndPoint);
}
export async function getUser(userId) {
const result = await http.get(apiEndPoint + "/" + userId);
return result.data;
}
This is the eventService file where the event is
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/events";
export function getEvents() {
return http.get(apiEndPoint);
}
export function getEvent(eventId) {
return http.get(apiEndPoint + "/" + eventId);
}
export function saveEvent(event) {
if(event._id){
const body = {...event}
delete body._id
return http.put(apiEndPoint + '/' + event._id, body)
}
return http.post(apiEndPoint, event);
}
export function deleteEvent(eventId) {
return http.delete(apiEndPoint + "/" + eventId);
}
First, you have some mistakes to use the class in <div> elements.
please use className instead class.
And then second I am not sure what it is.
class Events extends Component {
state = {
... ...
user: getUser(),
... ...
};
As you seen getUser() function requires one parameter userId.
But you did not send this.
So you met internal server error to do it.
Since I did not investigate all projects, I could not provide perfectly solution.
However, it is main reason, I think.
Please check it.

How to render the React component with dynamic data realtime from socket.io high efficiency

My front-end page is made by React + Flux, which sends the script data to back-end nodejs server.
The script data is an Array which contains the linux shell arguments (more than 100000). When to back-end received, it will execute the linux shell command.
Just an example:
cat ~/testfile1
cat ~/testfile2
.
.
.
(100000 times ...etc)
When the backend finished one of the linux shell commands, I can save the readed content to result data. Therefore, socket.io will emit the result data to the front-end.
I want to get the result data from my webpage in real time, so I have done some stuff in my project below.
My React component code:
import React from 'react';
import AppActions from '../../../actions/app-actions';
import SocketStore from '../../../stores/socket-store';
import ResultStore from '../../../stores/result-store';
function getSocket () {
return SocketStore.getSocket();
}
function getResult () {
return ResultStore.getResultItem();
}
class ListResultItem extends React.Component {
constructor () {
super();
}
render () {
return <li>
{this.props.result.get('name')} {this.props.result.get('txt')}
</li>;
}
}
class ShowResult extends React.Component {
constructor () {
super();
this.state = {
socket: getSocket(),
result: getResult()
};
}
componentWillMount () {
ResultStore.addChangeListener(this._onChange.bind(this));
}
_onChange () {
this.setState({
result: getResult()
});
}
render () {
return <div>
<ol>
{this.state.result.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})}
</ol>
</div>;
}
componentDidMount () {
this.state.socket.on('result', function (data) {
AppActions.addResult(data);
});
}
}
My Flux store (ResultStore) code:
import AppConstants from '../constants/app-constants.js';
import { dispatch, register } from '../dispatchers/app-dispatcher.js';
import { EventEmitter } from 'events';
import Immutable from 'immutable';
const CHANGE_EVENT = 'changeResult';
let _resultItem = Immutable.List();
const _addResult = (result) => {
let immObj = Immutable.fromJS(result);
_resultItem = _resultItem.push(immObj);
}
const _clearResult = () => {
_resultItem = _resultItem.clear();
}
const ResultStore = Object.assign(EventEmitter.prototype, {
emitChange (){
this.emit( CHANGE_EVENT );
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getResultItem () {
return _resultItem;
},
dispatcherIndex: register(function (action) {
switch (action.actionType) {
case AppConstants.ADD_RESULT:
_addResult(action.result);
break;
case AppConstants.CLEAR_RESULT:
_clearResult();
break;
}
ResultStore.emitChange();
})
});
However, the page will become very slow after rendering more than 1000 datas. How to enhance the performance for rendering? I need to execute the linux script persistently more than 3 days. Any solutions? Thanks~
Is there any need to render all the data on screen? If not then there are a few ways to deal with cutting down the amount of visible data.
Filter / Search
You can provide a search/filter component that complements the list and creates a predicate function that can be used to determine whether each item should or should not be rendered.
<PredicateList>
<Search />
<Filter />
{this.state.result
.filter(predicate)
.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})
}
</PredicateList>
Lazy Load
Load the items only when they are asked for. You can work out whether item is needed by keeping track of whether it would be onscreen, or whether the mouse was over it.
var Lazy = React.createClass({
getInitialState: function() {
return { loaded: false };
},
load: function() {
this.setState({ loaded: true });
},
render: function() {
var loaded = this.state.loaded,
component = this.props.children,
lazyContainer = <div onMouseEnter={this.load} />;
return loaded ?
component
lazyContainer;
}
});
Then simply wrap your data items inside these Lazy wrappers to have them render when they are requested.
<Lazy>
<ListResultItem key={index} result={item} />
</Lazy>
This ensures that only data needed by the user is seen. You could also improve the load trigger to work for more complex scenarios, such as when the component has been onscreen for more then 2 seconds.
Pagination
Finally, the last and most tried and tested approach is pagination. Choose a limit for a number of data items that can be shown in one go, then allow users to navigate through the data set in chunks.
var Paginate = React.createClass({
getDefaultProps: function() {
return { items: [], perPage: 100 };
},
getInitialState: function() {
return { page: 0 };
},
next: function() {
this.setState({ page: this.state.page + 1});
},
prev: function() {
this.setState({ page: this.state.page - 1});
},
render: function() {
var perPage = this.props.perPage,
currentPage = this.state.page,
itemCount = this.props.items.length;
var start = currentPage * perPage,
end = Math.min(itemCount, start + perPage);
var selectedItems = this.props.items.slice(start, end);
return (
<div className='pagination'>
{selectedItems.map(function(item, index) {
<ListResultItem key={index} result={item} />
})}
<a onClick={this.prev}>Previous {{this.state.perPage}} items</a>
<a onClick={this.next}>Next {{this.state.perPage}} items</a>
</div>
);
}
});
These are just very rough examples of implementations for managing the rendering of large amounts of data in efficient ways, but hopefully they will make enough sense for you to implement your own solution.

Resources